From e0c0b99a44401a63963083ed3572ffb690c73525 Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Thu, 26 Nov 2020 11:56:46 +0200 Subject: [PATCH 01/43] Use formencode.Invalid in all the examples for consistency --- docs/Validator.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/Validator.txt b/docs/Validator.txt index 044b9a89..22c0984f 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -325,14 +325,14 @@ that a little more. Here's a more complete implementation of ... ... def _validate_python(self, value, state): ... if len(value) < self.min: - ... raise Invalid(self.message("too_few", state, - ... min=self.min), - ... value, state) + ... raise formencode.Invalid(self.message("too_few", state, + ... min=self.min), + ... value, state) ... non_letters = self.letter_regex.sub('', value) ... if len(non_letters) < self.non_letter: - ... raise Invalid(self.message("non_letter", state, - ... non_letter=self.non_letter), - ... value, state) + ... raise formencode.Invalid(self.message("non_letter", state, + ... non_letter=self.non_letter), + ... value, state) With all validators, any arguments you pass to the constructor will be used to set instance variables. So :class:`SecureValidator(min=5)` will @@ -502,8 +502,8 @@ use the :meth:`message` method, like:: } def _validate_python(self, value, state): - raise Invalid(self.message('key', state, substitution='apples'), - value, state) + raise formencode.Invalid(self.message('key', state, substitution='apples'), + value, state) Localization of Error Messages (i18n) ------------------------------------- From f8f62b4c0b35f7bf7c08fd85b912ba6cfe7b5f74 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 26 Nov 2020 11:30:50 +0100 Subject: [PATCH 02/43] Fix a minor ReST issue in the new changelog --- docs/whatsnew-2.0.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 55b6233b..1923babb 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -1,5 +1,5 @@ What's New In FormEncode 2.0 -============================== +============================ This article explains the latest changes in `FormEncode` version 2.0.0 compared to its predecessor, `FormEncode` 1.3.0 @@ -9,10 +9,9 @@ Changelog --------- - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. If - you need Python 2.6 or 3.2 to 3.5 compatibility please use FormEncode 1.3. -You might also try FormEncode 2.0.0a1 which supports Python 2.6 and Python -3.3-3.5 + - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. + If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. + You might also try `FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. - This will be the last major version to support Python 2.7 - Add strict flag to USPostalCode to raise error on postal codes that has too many digits instead of just truncating From 74bbd2ea74aa39792136fbdc0aa5fd40da1510e2 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sun, 29 Nov 2020 17:01:48 -0500 Subject: [PATCH 03/43] Declare 3.9 support and prep for 3.10 support Closes #160 --- .travis.yml | 1 + setup.py | 9 +++++---- tox.ini | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 637e3bea..2e115089 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - 3.6 - 3.7 - 3.8 + - 3.9 - pypy install: pip install . diff --git a/setup.py b/setup.py index f0ec674d..976d3ff7 100755 --- a/setup.py +++ b/setup.py @@ -10,17 +10,17 @@ import sys from setuptools import setup, find_packages -if not '2.7' <= sys.version < '3.0' and not '3.6' <= sys.version: +if not (2,7) <= sys.version_info[:2] < (3,0) and not (3,6) <= sys.version_info[:2]: raise ImportError('Python version not supported') -tests_require = ['nose', 'dnspython==1.16.0' if sys.version < '3.0' else 'dnspython>=2.0.0', - 'pycountry<19' if sys.version < '3.0' else 'pycountry'] +tests_require = ['nose', 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', + 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] doctests = ['docs/htmlfill.txt', 'docs/Validator.txt', 'formencode/tests/non_empty.txt'] setup(name='FormEncode', - # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,' # PEP345 + # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,!=3.5' # PEP345 description="HTML form validation, generation, and conversion package", long_description=__doc__, classifiers=[ @@ -34,6 +34,7 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", ], author='Ian Bicking', diff --git a/tox.ini b/tox.ini index 3b05face..c53dd196 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,pypy,py36,py37,py38 +envlist=py27,pypy,py36,py37,py38,py39 [testenv] deps= From d3e76b1d777736c63197eb429443ce0a009d50cd Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 7 Dec 2020 08:41:51 -0500 Subject: [PATCH 04/43] Add 3.10 and pypy3 to test matrix --- .travis.yml | 2 ++ tox.ini | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e115089..c799dec3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ python: - 3.7 - 3.8 - 3.9 + - 3.10-dev - pypy + - pypy3 install: pip install . diff --git a/tox.ini b/tox.ini index c53dd196..4acbd71f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,pypy,py36,py37,py38,py39 +envlist=py27,pypy,py36,py37,py38,py39,py310,pypy3 [testenv] deps= @@ -7,7 +7,7 @@ deps= wheel py27,pypy: pycountry < 19 py27,pypy: dnspython < 2.0.0 - py{36,37,38}: pycountry dnspython + py{36,37,38,39,310},pypy3: pycountry dnspython commands= python setup.py clean --all python setup.py nosetests From 9cb3cdbc24502bdf216dbf6484c74419b5585bc6 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 4 Oct 2021 21:32:48 -0400 Subject: [PATCH 05/43] pytest instead of nosetest and github actions --- .github/workflows/run-tests.yml | 31 +++++++++++++++++++++++++++++ .gitignore | 1 + .travis.yml | 16 --------------- Makefile | 22 ++++++++++++++++++++ README.rst | 2 +- formencode/tests/test_context.py | 5 +++-- formencode/tests/test_doctests.py | 15 +++++++++----- formencode/tests/test_htmlfill.py | 10 ++++++---- formencode/tests/test_schema.py | 12 ++++++++--- formencode/tests/test_validators.py | 4 ++-- formencode/validators.py | 2 +- pytest.ini | 3 +++ requirements-dev.txt | 9 +++++++++ run-tests-generate-coverage-html | 3 --- setup.cfg | 3 --- setup.py | 14 ++++++++++--- tox.ini | 6 +++--- 17 files changed, 112 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/run-tests.yml delete mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 pytest.ini create mode 100644 requirements-dev.txt delete mode 100755 run-tests-generate-coverage-html diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000..ad2000ab --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,31 @@ +name: Tests + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10-dev] + os: [ubuntu-18.04, macOS-latest, windows-latest] + include: + # pypy3 on Windows currently fails trying to + # run dnspython tests. Moving pypy3 to only test linux. + - python-version: pypy3 + os: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + make + - name: Run tests + run: | + make ci diff --git a/.gitignore b/.gitignore index 0e6e090a..619c6219 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ docs/_build .project .pydevproject .settings +.tool-versions diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c799dec3..00000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: python - -python: - - 2.7 - - 3.6 - - 3.7 - - 3.8 - - 3.9 - - 3.10-dev - - pypy - - pypy3 - -install: pip install . - -script: python setup.py nosetests - diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8603ed14 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +.PHONY: docs +init: + pip install -e . + pip install -r requirements-dev.txt +ci: + pytest formencode + +flake8: + flake8 --ignore=E501,F401,E128,E402,E731,F821 formencode + +coverage: + pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode + +publish: + pip install 'twine>=1.5.0' + python setup.py sdist bdist_wheel + twine upload dist/* + rm -fr build dist .egg formencode.egg-info + +docs: + cd docs && make html + @echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m" diff --git a/README.rst b/README.rst index 7c7a764c..79fc8cb4 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ The latest documentation is available at http://www.formencode.org/ Testing ------- -Use `python setup.py nosetests` to run the test suite. +Use `pytest formencode` to run the test suite. Use `tox` to run the test suite for all supported Python versions. diff --git a/formencode/tests/test_context.py b/formencode/tests/test_context.py index 5fcd52d5..4c2a28c9 100644 --- a/formencode/tests/test_context.py +++ b/formencode/tests/test_context.py @@ -1,5 +1,5 @@ from __future__ import absolute_import -from nose.tools import assert_raises +import pytest from formencode.context import Context, ContextRestoreError @@ -34,7 +34,8 @@ def test_fail(): c3 = Context() res1 = c3.set(a=1) res2 = c3.set(b=2) - assert_raises(ContextRestoreError, res1.restore) + with pytest.raises(ContextRestoreError): + res1.restore() assert c3.b == 2 assert c3.a == 1 res2.restore() diff --git a/formencode/tests/test_doctests.py b/formencode/tests/test_doctests.py index 800eb04b..a3288ae9 100644 --- a/formencode/tests/test_doctests.py +++ b/formencode/tests/test_doctests.py @@ -11,6 +11,8 @@ from formencode import validators import six +import pytest + """Modules that will have their doctests tested.""" modules = [compound, htmlfill, htmlgen, national, schema, validators] @@ -66,16 +68,13 @@ def doctest_module(document, verbose, raise_error): def set_func_description(fn, description): - """Wrap function and set description attr for nosetests to display.""" + """Wrap function and set description attr for pytest to display.""" def _wrapper(*a_test_args): fn(*a_test_args) _wrapper.description = description return _wrapper - -def test_doctests(): - """Generate each doctest.""" - # TODO Can we resolve this from nose? +def collect_functions(): verbose = False raise_error = True for document in text_files + modules: @@ -91,6 +90,12 @@ def test_doctests(): verbose, raise_error +@pytest.mark.parametrize("testfn,document,verbose,raise_error", list(collect_functions())) +def test_doctests(testfn,document,verbose,raise_error): + """Generate each doctest.""" + testfn(document, verbose, raise_error) + + if __name__ == '__main__': # Call this file directly if you want to test doctests. args = sys.argv[1:] diff --git a/formencode/tests/test_htmlfill.py b/formencode/tests/test_htmlfill.py index 431e03bf..ecade0a1 100644 --- a/formencode/tests/test_htmlfill.py +++ b/formencode/tests/test_htmlfill.py @@ -24,16 +24,18 @@ from formencode import htmlfill, htmlfill_schemabuilder from formencode.doctest_xml_compare import xml_compare +import pytest -def test_inputoutput(): + +def collect_values(): 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 - + yield fn -def run_filename(filename): +@pytest.mark.parametrize('filename', list(collect_values())) +def test_runfile(filename): f = open(filename) content = f.read() f.close() diff --git a/formencode/tests/test_schema.py b/formencode/tests/test_schema.py index 56b153cb..2ed349a2 100644 --- a/formencode/tests/test_schema.py +++ b/formencode/tests/test_schema.py @@ -8,6 +8,8 @@ from formencode.schema import Schema, merge_dicts, SimpleFormValidator from formencode.variabledecode import NestedVariables +import pytest + def _notranslation(s): return s @@ -146,10 +148,14 @@ class schema(Schema): text="The input field 'whatever' was not expected.") -def test_this(): - +def collect_cases(): for case in all_cases: - yield (case.test,) + yield case.test + + +@pytest.mark.parametrize('func', list(collect_cases())) +def test_this(func): + func() def test_merge(): diff --git a/formencode/tests/test_validators.py b/formencode/tests/test_validators.py index cc653192..fd7d186d 100644 --- a/formencode/tests/test_validators.py +++ b/formencode/tests/test_validators.py @@ -4,7 +4,7 @@ import datetime import unittest -from nose.plugins.skip import SkipTest +import pytest from formencode import validators from formencode.validators import Invalid @@ -198,7 +198,7 @@ def test_inf(self): try: inf = float('infinity') except ValueError: - raise SkipTest + pytest.skip('skipping incompatible float test') self.assertEqual(self.validator.to_python('infinity'), inf) diff --git a/formencode/validators.py b/formencode/validators.py index ff58642c..11222878 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1395,7 +1395,7 @@ def _convert_to_python(self, value, state): class URL(FancyValidator): - """ + r""" Validate a URL, either http://... or https://. If check_exists is true, then we'll actually make a request for the page. diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..787b78f4 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +# content of pytest.ini +[pytest] +#addopts = --doctest-modules diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..cfd0c5e0 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,9 @@ +pytest<4.7; python_version == '2.7' +pytest-cov<2.13; python_version == '2.7' +pytest; python_version > '2.7' +pytest-cov; python_version > '2.7' +wheel +pycountry<19; python_version == '2.7' +dnspython<2.0.0; python_version == '2.7' +pycountry; python_version > '2.7' +dnspython; python_version > '2.7' diff --git a/run-tests-generate-coverage-html b/run-tests-generate-coverage-html deleted file mode 100755 index a73069f1..00000000 --- a/run-tests-generate-coverage-html +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -nosetests --with-coverage --cover-package=formencode --cover-erase --cover-html --cover-html-dir=coverage \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index b2aba26e..d757f58b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[nosetests] -detailed-errors = 1 - # Babel configuration [compile_catalog] domain = FormEncode diff --git a/setup.py b/setup.py index 976d3ff7..99ae4692 100755 --- a/setup.py +++ b/setup.py @@ -9,12 +9,20 @@ import sys from setuptools import setup, find_packages +import platform if not (2,7) <= sys.version_info[:2] < (3,0) and not (3,6) <= sys.version_info[:2]: raise ImportError('Python version not supported') -tests_require = ['nose', 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', - 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] +tests_require = [ + 'pytest<4.7' if sys.version_info[:2] < (3,0) else 'pytest', + 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', + 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] + +setup_requires = [ + 'setuptools_scm<6.0' if sys.version_info[:2] < (3,0) else 'setuptools_scm', + 'setuptools_scm_git_archive', +] doctests = ['docs/htmlfill.txt', 'docs/Validator.txt', 'formencode/tests/non_empty.txt'] @@ -50,7 +58,7 @@ install_requires=['six'], tests_require=tests_require, use_scm_version=True, - setup_requires=['setuptools_scm', 'setuptools_scm_git_archive'], + setup_requires=setup_requires, extras_require={'testing': tests_require}, ) diff --git a/tox.ini b/tox.ini index 4acbd71f..73dc6c9f 100644 --- a/tox.ini +++ b/tox.ini @@ -3,11 +3,11 @@ envlist=py27,pypy,py36,py37,py38,py39,py310,pypy3 [testenv] deps= - nose + pytest wheel py27,pypy: pycountry < 19 py27,pypy: dnspython < 2.0.0 py{36,37,38,39,310},pypy3: pycountry dnspython commands= - python setup.py clean --all - python setup.py nosetests + pip install -e . + pytest formencode From 483b404a6a8bca03f376627c1178cbb1563991fe Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Tue, 5 Oct 2021 08:31:49 -0400 Subject: [PATCH 06/43] Prep for 2.0.1 release to support Python 3.10 --- .github/workflows/run-tests.yml | 8 ++++++-- Makefile | 2 +- README.rst | 12 ++++-------- docs/whatsnew-2.0.txt | 13 +++++++++++-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ad2000ab..c1e62132 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,6 +1,10 @@ name: Tests -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: {} jobs: build: @@ -9,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10-dev] + python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10.0] os: [ubuntu-18.04, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to diff --git a/Makefile b/Makefile index 8603ed14..e0ff995d 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ publish: pip install 'twine>=1.5.0' python setup.py sdist bdist_wheel twine upload dist/* - rm -fr build dist .egg formencode.egg-info + rm -fr build dist .egg FormEncode.egg-info docs: cd docs && make html diff --git a/README.rst b/README.rst index 79fc8cb4..9b556630 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,9 @@ FormEncode ========== -.. image:: https://secure.travis-ci.org/formencode/formencode.png?branch=master - :target: https://travis-ci.org/formencode/formencode - :alt: Travis CI continuous integration status +.. image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg + :target: https://github.com/formencode/formencode/actions + :alt: Test Status Introduction @@ -31,8 +31,4 @@ Use `tox` to run the test suite for all supported Python versions. Changes ------- -Added a validator that can require one or more fields based on the value of another field. - -A German howto can be found here: http://techblog.auf-nach-mallorca.info/2014/08/19/dynamische_formulare_validieren_mit_formencode/ - -Courtesy of the developers of http://www.auf-nach-mallorca.info +See the `What's new section of the documentation `_. diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 1923babb..7b5adcc8 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -5,8 +5,17 @@ This article explains the latest changes in `FormEncode` version 2.0.0 compared to its predecessor, `FormEncode` 1.3.0 -Changelog ---------- +2.0.1 +----- + - Add support for 3.10 + - use Pytest instead of Nose and Github Actions instead of Travis for tests + - Documentation updates + - Note this will be the last version to support Python 2.7. The next version + will be 2.1 to signal this change. If you want to keep support for Python + 2.7 update your dependencies spec to be below 2.1 + +2.0.0 +----- - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. From 25749f8ebb5d0aebf484f901dcdefa6017a5bf7c Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 3 Jan 2022 21:01:38 -0500 Subject: [PATCH 07/43] Python 3.11 and setup.cfg instead of setup.py --- .github/workflows/run-tests.yml | 6 +- Makefile | 12 ++-- pyproject.toml | 5 ++ requirements-dev.txt | 12 ++-- requirements-test.txt | 5 ++ setup.cfg | 56 ++++++++++++--- setup.py | 58 +--------------- {formencode => src/formencode}/__init__.py | 0 {formencode => src/formencode}/api.py | 0 {formencode => src/formencode}/compound.py | 0 {formencode => src/formencode}/context.py | 0 {formencode => src/formencode}/declarative.py | 0 .../formencode}/doctest_xml_compare.py | 0 {formencode => src/formencode}/exc.py | 0 .../formencode}/fieldstorage.py | 0 {formencode => src/formencode}/foreach.py | 0 {formencode => src/formencode}/htmlfill.py | 0 .../formencode}/htmlfill_schemabuilder.py | 0 {formencode => src/formencode}/htmlgen.py | 0 {formencode => src/formencode}/htmlrename.py | 0 .../formencode}/i18n/FormEncode.pot | 0 .../i18n/cs/LC_MESSAGES/FormEncode.mo | Bin .../i18n/cs/LC_MESSAGES/FormEncode.po | 0 .../i18n/da/LC_MESSAGES/FormEncode.mo | Bin .../i18n/da/LC_MESSAGES/FormEncode.po | 0 .../i18n/de/LC_MESSAGES/FormEncode.mo | Bin .../i18n/de/LC_MESSAGES/FormEncode.po | 0 .../i18n/el/LC_MESSAGES/FormEncode.mo | Bin .../i18n/el/LC_MESSAGES/FormEncode.po | 0 .../i18n/es/LC_MESSAGES/FormEncode.mo | Bin .../i18n/es/LC_MESSAGES/FormEncode.po | 0 .../i18n/et/LC_MESSAGES/FormEncode.mo | Bin .../i18n/et/LC_MESSAGES/FormEncode.po | 0 .../i18n/fi/LC_MESSAGES/FormEncode.mo | Bin .../i18n/fi/LC_MESSAGES/FormEncode.po | 0 .../i18n/fr/LC_MESSAGES/FormEncode.mo | Bin .../i18n/fr/LC_MESSAGES/FormEncode.po | 0 .../i18n/it/LC_MESSAGES/FormEncode.mo | Bin .../i18n/it/LC_MESSAGES/FormEncode.po | 0 .../i18n/ja/LC_MESSAGES/FormEncode.mo | Bin .../i18n/ja/LC_MESSAGES/FormEncode.po | 0 .../i18n/ko/LC_MESSAGES/FormEncode.mo | Bin .../i18n/ko/LC_MESSAGES/FormEncode.po | 0 .../i18n/lt/LC_MESSAGES/FormEncode.mo | Bin .../i18n/lt/LC_MESSAGES/FormEncode.po | 0 .../i18n/mk/LC_MESSAGES/FormEncode.mo | Bin .../i18n/mk/LC_MESSAGES/FormEncode.po | 0 .../i18n/nb_NO/LC_MESSAGES/FormEncode.mo | Bin .../i18n/nb_NO/LC_MESSAGES/FormEncode.po | 0 .../i18n/nl/LC_MESSAGES/FormEncode.mo | Bin .../i18n/nl/LC_MESSAGES/FormEncode.po | 0 .../i18n/pl/LC_MESSAGES/FormEncode.mo | Bin .../i18n/pl/LC_MESSAGES/FormEncode.po | 0 .../i18n/pt_BR/LC_MESSAGES/FormEncode.mo | Bin .../i18n/pt_BR/LC_MESSAGES/FormEncode.po | 0 .../i18n/pt_PT/LC_MESSAGES/FormEncode.mo | Bin .../i18n/pt_PT/LC_MESSAGES/FormEncode.po | 0 .../i18n/ru/LC_MESSAGES/FormEncode.mo | Bin .../i18n/ru/LC_MESSAGES/FormEncode.po | 0 .../i18n/sk/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sk/LC_MESSAGES/FormEncode.po | 0 .../i18n/sl/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sl/LC_MESSAGES/FormEncode.po | 0 .../i18n/sr/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sr/LC_MESSAGES/FormEncode.po | 0 .../i18n/sr@latin/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sr@latin/LC_MESSAGES/FormEncode.po | 0 .../i18n/sv/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sv/LC_MESSAGES/FormEncode.po | 0 .../i18n/tr/LC_MESSAGES/FormEncode.mo | Bin .../i18n/tr/LC_MESSAGES/FormEncode.po | 0 .../i18n/zh_CN/LC_MESSAGES/FormEncode.mo | Bin .../i18n/zh_CN/LC_MESSAGES/FormEncode.po | 0 .../i18n/zh_TW/LC_MESSAGES/FormEncode.mo | Bin .../i18n/zh_TW/LC_MESSAGES/FormEncode.po | 0 {formencode => src/formencode}/interfaces.py | 0 .../formencode}/javascript/ordering.js | 0 {formencode => src/formencode}/national.py | 0 .../formencode}/rewritingparser.py | 0 {formencode => src/formencode}/schema.py | 0 {formencode => src/formencode}/validators.py | 0 .../formencode}/variabledecode.py | 0 {formencode/tests => tests}/__init__.py | 0 .../htmlfill_data/data-error1.txt | 0 .../htmlfill_data/data-fill1.txt | 0 .../htmlfill_data/data-fill2.txt | 0 .../htmlfill_data/data-fill3.txt | 0 .../htmlfill_data/data-fill4.txt | 0 .../htmlfill_data/data-form-last-element.txt | 0 .../htmlfill_data/data-schema1.txt | 0 {formencode/tests => tests}/non_empty.txt | 0 {formencode/tests => tests}/test_cc.py | 0 {formencode/tests => tests}/test_compound.py | 0 {formencode/tests => tests}/test_context.py | 0 .../tests => tests}/test_declarative.py | 0 .../test_doctest_xml_compare.py | 0 {formencode/tests => tests}/test_doctests.py | 65 +++++++++++------- {formencode/tests => tests}/test_email.py | 0 {formencode/tests => tests}/test_htmlfill.py | 0 .../tests => tests}/test_htmlfill_control.py | 0 {formencode/tests => tests}/test_htmlgen.py | 0 .../tests => tests}/test_htmlrename.py | 0 {formencode/tests => tests}/test_i18n.py | 0 {formencode/tests => tests}/test_schema.py | 0 .../tests => tests}/test_subclassing.py | 0 .../tests => tests}/test_subclassing_old.py | 0 .../tests => tests}/test_validators.py | 0 .../tests => tests}/test_variabledecode.py | 0 tox.ini | 7 +- 109 files changed, 116 insertions(+), 110 deletions(-) create mode 100644 pyproject.toml create mode 100644 requirements-test.txt rename {formencode => src/formencode}/__init__.py (100%) rename {formencode => src/formencode}/api.py (100%) rename {formencode => src/formencode}/compound.py (100%) rename {formencode => src/formencode}/context.py (100%) rename {formencode => src/formencode}/declarative.py (100%) rename {formencode => src/formencode}/doctest_xml_compare.py (100%) rename {formencode => src/formencode}/exc.py (100%) rename {formencode => src/formencode}/fieldstorage.py (100%) rename {formencode => src/formencode}/foreach.py (100%) rename {formencode => src/formencode}/htmlfill.py (100%) rename {formencode => src/formencode}/htmlfill_schemabuilder.py (100%) rename {formencode => src/formencode}/htmlgen.py (100%) rename {formencode => src/formencode}/htmlrename.py (100%) rename {formencode => src/formencode}/i18n/FormEncode.pot (100%) rename {formencode => src/formencode}/i18n/cs/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/cs/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/da/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/da/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/de/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/de/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/el/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/el/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/es/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/es/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/et/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/et/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/fi/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/fi/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/fr/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/fr/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/it/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/it/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/ja/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/ja/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/ko/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/ko/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/lt/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/lt/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/mk/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/mk/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/nb_NO/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/nb_NO/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/nl/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/nl/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/pl/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/pl/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/pt_BR/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/pt_BR/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/pt_PT/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/pt_PT/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/ru/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/ru/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sk/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sk/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sl/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sl/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sr/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sr/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sr@latin/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sr@latin/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sv/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sv/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/tr/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/tr/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/zh_CN/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/zh_CN/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/zh_TW/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/zh_TW/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/interfaces.py (100%) rename {formencode => src/formencode}/javascript/ordering.js (100%) rename {formencode => src/formencode}/national.py (100%) rename {formencode => src/formencode}/rewritingparser.py (100%) rename {formencode => src/formencode}/schema.py (100%) rename {formencode => src/formencode}/validators.py (100%) rename {formencode => src/formencode}/variabledecode.py (100%) rename {formencode/tests => tests}/__init__.py (100%) rename {formencode/tests => tests}/htmlfill_data/data-error1.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill1.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill2.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill3.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill4.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-form-last-element.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-schema1.txt (100%) rename {formencode/tests => tests}/non_empty.txt (100%) rename {formencode/tests => tests}/test_cc.py (100%) rename {formencode/tests => tests}/test_compound.py (100%) rename {formencode/tests => tests}/test_context.py (100%) rename {formencode/tests => tests}/test_declarative.py (100%) rename {formencode/tests => tests}/test_doctest_xml_compare.py (100%) rename {formencode/tests => tests}/test_doctests.py (65%) rename {formencode/tests => tests}/test_email.py (100%) rename {formencode/tests => tests}/test_htmlfill.py (100%) rename {formencode/tests => tests}/test_htmlfill_control.py (100%) rename {formencode/tests => tests}/test_htmlgen.py (100%) rename {formencode/tests => tests}/test_htmlrename.py (100%) rename {formencode/tests => tests}/test_i18n.py (100%) rename {formencode/tests => tests}/test_schema.py (100%) rename {formencode/tests => tests}/test_subclassing.py (100%) rename {formencode/tests => tests}/test_subclassing_old.py (100%) rename {formencode/tests => tests}/test_validators.py (100%) rename {formencode/tests => tests}/test_variabledecode.py (100%) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c1e62132..6635747e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,12 +13,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10.0] + python-version: [3.7, 3.8, 3.9, '3.10.0 - 3.10.99', '3.11.0-alpha - 3.11.0'] os: [ubuntu-18.04, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. - - python-version: pypy3 + - python-version: pypy-3.7 + os: ubuntu-latest + - python-version: pypy-3.8 os: ubuntu-latest steps: diff --git a/Makefile b/Makefile index e0ff995d..eb9713c4 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,21 @@ .PHONY: docs init: pip install -e . - pip install -r requirements-dev.txt + pip install -r requirements-test.txt ci: - pytest formencode + pytest flake8: - flake8 --ignore=E501,F401,E128,E402,E731,F821 formencode + flake8 --ignore=E501,F401,E128,E402,E731,F821 src/formencode coverage: pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode publish: - pip install 'twine>=1.5.0' - python setup.py sdist bdist_wheel + pip install 'twine>=1.5.0' build + python -m build twine upload dist/* - rm -fr build dist .egg FormEncode.egg-info + rm -fr build dist .egg src/FormEncode.egg-info docs: cd docs && make html diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c19dbc32 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[build-system] +requires = ["setuptools", "wheel", "setuptools_scm>=6.2"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] diff --git a/requirements-dev.txt b/requirements-dev.txt index cfd0c5e0..c40fa96e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,9 +1,5 @@ -pytest<4.7; python_version == '2.7' -pytest-cov<2.13; python_version == '2.7' -pytest; python_version > '2.7' -pytest-cov; python_version > '2.7' +build wheel -pycountry<19; python_version == '2.7' -dnspython<2.0.0; python_version == '2.7' -pycountry; python_version > '2.7' -dnspython; python_version > '2.7' +black +flake8 +-r requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 00000000..d858886e --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,5 @@ +pytest +pytest-cov +wheel +pycountry +dnspython diff --git a/setup.cfg b/setup.cfg index d757f58b..de307aea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,48 @@ +[metadata] +name = FormEncode +description = "HTML form validation, generation, and conversion package" +long_description = file: README.rst +long_description_content_type = text/x-rst +author = Ian Bicking +author_email = ianb@colorstudy.com +maintainer = Chris Lambacher +maintainer_email = chris@kateandchris.net +url = "http://formencode.org" +license = MIT +license_file = LICENSE.txt +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + 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 + Programming Language :: Python :: Implementation :: PyPy + Topic :: Software Development :: Libraries :: Python Modules + +[options] +packages = find: +package_dir = + =src +python_requires = >=3.7 +install_requires = six +include_package_data = True + +[options.packages.find] +where = src + +[options.extras_require] +testing = + pytest + dnspython + pycountry + # Babel configuration [compile_catalog] domain = FormEncode @@ -22,14 +67,9 @@ input_file = formencode/i18n/FormEncode.pot output_dir = formencode/i18n previous = true -[wheel] -universal = 1 - [pep8] -max_line_length=100 +max_line_length=88 [flake8] -max_line_length=100 - -[metadata] -license_files = LICENSE.txt +max-line-length = 88 +extend-ignore = E203 diff --git a/setup.py b/setup.py index 99ae4692..0b62acc5 100755 --- a/setup.py +++ b/setup.py @@ -5,60 +5,6 @@ The official repo is at GitHub: https://github.com/formencode/formencode """ -from __future__ import absolute_import +from setuptools import setup -import sys -from setuptools import setup, find_packages -import platform - -if not (2,7) <= sys.version_info[:2] < (3,0) and not (3,6) <= sys.version_info[:2]: - raise ImportError('Python version not supported') - -tests_require = [ - 'pytest<4.7' if sys.version_info[:2] < (3,0) else 'pytest', - 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', - 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] - -setup_requires = [ - 'setuptools_scm<6.0' if sys.version_info[:2] < (3,0) else 'setuptools_scm', - 'setuptools_scm_git_archive', -] - -doctests = ['docs/htmlfill.txt', 'docs/Validator.txt', - 'formencode/tests/non_empty.txt'] - -setup(name='FormEncode', - # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,!=3.5' # PEP345 - description="HTML form validation, generation, and conversion package", - long_description=__doc__, - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - author='Ian Bicking', - author_email='ianb@colorstudy.com', - url='http://formencode.org', - license='MIT', - data_files = [("", ["LICENSE.txt"])], - zip_safe=False, - packages=find_packages(), - include_package_data=True, - package_data={'formencode': ['../docs/*.txt']}, - test_suite='formencode.tests', - install_requires=['six'], - tests_require=tests_require, - use_scm_version=True, - setup_requires=setup_requires, - - extras_require={'testing': tests_require}, - ) +setup() diff --git a/formencode/__init__.py b/src/formencode/__init__.py similarity index 100% rename from formencode/__init__.py rename to src/formencode/__init__.py diff --git a/formencode/api.py b/src/formencode/api.py similarity index 100% rename from formencode/api.py rename to src/formencode/api.py diff --git a/formencode/compound.py b/src/formencode/compound.py similarity index 100% rename from formencode/compound.py rename to src/formencode/compound.py diff --git a/formencode/context.py b/src/formencode/context.py similarity index 100% rename from formencode/context.py rename to src/formencode/context.py diff --git a/formencode/declarative.py b/src/formencode/declarative.py similarity index 100% rename from formencode/declarative.py rename to src/formencode/declarative.py diff --git a/formencode/doctest_xml_compare.py b/src/formencode/doctest_xml_compare.py similarity index 100% rename from formencode/doctest_xml_compare.py rename to src/formencode/doctest_xml_compare.py diff --git a/formencode/exc.py b/src/formencode/exc.py similarity index 100% rename from formencode/exc.py rename to src/formencode/exc.py diff --git a/formencode/fieldstorage.py b/src/formencode/fieldstorage.py similarity index 100% rename from formencode/fieldstorage.py rename to src/formencode/fieldstorage.py diff --git a/formencode/foreach.py b/src/formencode/foreach.py similarity index 100% rename from formencode/foreach.py rename to src/formencode/foreach.py diff --git a/formencode/htmlfill.py b/src/formencode/htmlfill.py similarity index 100% rename from formencode/htmlfill.py rename to src/formencode/htmlfill.py diff --git a/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py similarity index 100% rename from formencode/htmlfill_schemabuilder.py rename to src/formencode/htmlfill_schemabuilder.py diff --git a/formencode/htmlgen.py b/src/formencode/htmlgen.py similarity index 100% rename from formencode/htmlgen.py rename to src/formencode/htmlgen.py diff --git a/formencode/htmlrename.py b/src/formencode/htmlrename.py similarity index 100% rename from formencode/htmlrename.py rename to src/formencode/htmlrename.py diff --git a/formencode/i18n/FormEncode.pot b/src/formencode/i18n/FormEncode.pot similarity index 100% rename from formencode/i18n/FormEncode.pot rename to src/formencode/i18n/FormEncode.pot diff --git a/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/cs/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/cs/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/cs/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/cs/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/cs/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/da/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/da/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/da/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/da/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/da/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/da/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/da/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/da/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/de/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/de/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/de/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/de/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/de/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/de/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/de/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/de/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/el/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/el/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/el/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/el/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/el/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/el/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/el/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/el/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/es/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/es/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/es/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/es/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/es/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/es/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/es/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/es/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/et/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/et/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/et/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/et/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/et/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/et/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/et/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/et/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/fi/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/fi/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/fi/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/fi/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/fi/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/fr/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/fr/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/fr/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/fr/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/fr/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/it/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/it/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/it/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/it/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/it/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/it/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/it/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/it/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/ja/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/ja/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/ja/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/ja/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/ja/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/ko/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/ko/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/ko/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/ko/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/ko/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/ko/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/ko/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/ko/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/lt/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/lt/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/lt/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/lt/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/lt/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/mk/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/mk/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/mk/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/mk/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/mk/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/nl/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/nl/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/nl/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/nl/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/nl/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/nl/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/nl/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/nl/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/pl/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/pl/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/pl/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/pl/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/pl/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/pl/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/pl/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/pl/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/ru/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/ru/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/ru/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/ru/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/ru/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/ru/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/ru/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/ru/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sk/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sk/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sk/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sk/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sk/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sk/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sk/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sk/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sl/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sl/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sl/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sl/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sl/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sl/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sl/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sl/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sr/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sr/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sr/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sr/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sr/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sv/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sv/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sv/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sv/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/tr/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/tr/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/tr/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/tr/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/tr/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/tr/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/tr/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/tr/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po diff --git a/formencode/interfaces.py b/src/formencode/interfaces.py similarity index 100% rename from formencode/interfaces.py rename to src/formencode/interfaces.py diff --git a/formencode/javascript/ordering.js b/src/formencode/javascript/ordering.js similarity index 100% rename from formencode/javascript/ordering.js rename to src/formencode/javascript/ordering.js diff --git a/formencode/national.py b/src/formencode/national.py similarity index 100% rename from formencode/national.py rename to src/formencode/national.py diff --git a/formencode/rewritingparser.py b/src/formencode/rewritingparser.py similarity index 100% rename from formencode/rewritingparser.py rename to src/formencode/rewritingparser.py diff --git a/formencode/schema.py b/src/formencode/schema.py similarity index 100% rename from formencode/schema.py rename to src/formencode/schema.py diff --git a/formencode/validators.py b/src/formencode/validators.py similarity index 100% rename from formencode/validators.py rename to src/formencode/validators.py diff --git a/formencode/variabledecode.py b/src/formencode/variabledecode.py similarity index 100% rename from formencode/variabledecode.py rename to src/formencode/variabledecode.py diff --git a/formencode/tests/__init__.py b/tests/__init__.py similarity index 100% rename from formencode/tests/__init__.py rename to tests/__init__.py diff --git a/formencode/tests/htmlfill_data/data-error1.txt b/tests/htmlfill_data/data-error1.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-error1.txt rename to tests/htmlfill_data/data-error1.txt diff --git a/formencode/tests/htmlfill_data/data-fill1.txt b/tests/htmlfill_data/data-fill1.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill1.txt rename to tests/htmlfill_data/data-fill1.txt diff --git a/formencode/tests/htmlfill_data/data-fill2.txt b/tests/htmlfill_data/data-fill2.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill2.txt rename to tests/htmlfill_data/data-fill2.txt diff --git a/formencode/tests/htmlfill_data/data-fill3.txt b/tests/htmlfill_data/data-fill3.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill3.txt rename to tests/htmlfill_data/data-fill3.txt diff --git a/formencode/tests/htmlfill_data/data-fill4.txt b/tests/htmlfill_data/data-fill4.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill4.txt rename to tests/htmlfill_data/data-fill4.txt diff --git a/formencode/tests/htmlfill_data/data-form-last-element.txt b/tests/htmlfill_data/data-form-last-element.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-form-last-element.txt rename to tests/htmlfill_data/data-form-last-element.txt diff --git a/formencode/tests/htmlfill_data/data-schema1.txt b/tests/htmlfill_data/data-schema1.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-schema1.txt rename to tests/htmlfill_data/data-schema1.txt diff --git a/formencode/tests/non_empty.txt b/tests/non_empty.txt similarity index 100% rename from formencode/tests/non_empty.txt rename to tests/non_empty.txt diff --git a/formencode/tests/test_cc.py b/tests/test_cc.py similarity index 100% rename from formencode/tests/test_cc.py rename to tests/test_cc.py diff --git a/formencode/tests/test_compound.py b/tests/test_compound.py similarity index 100% rename from formencode/tests/test_compound.py rename to tests/test_compound.py diff --git a/formencode/tests/test_context.py b/tests/test_context.py similarity index 100% rename from formencode/tests/test_context.py rename to tests/test_context.py diff --git a/formencode/tests/test_declarative.py b/tests/test_declarative.py similarity index 100% rename from formencode/tests/test_declarative.py rename to tests/test_declarative.py diff --git a/formencode/tests/test_doctest_xml_compare.py b/tests/test_doctest_xml_compare.py similarity index 100% rename from formencode/tests/test_doctest_xml_compare.py rename to tests/test_doctest_xml_compare.py diff --git a/formencode/tests/test_doctests.py b/tests/test_doctests.py similarity index 65% rename from formencode/tests/test_doctests.py rename to tests/test_doctests.py index a3288ae9..6efb38fb 100644 --- a/formencode/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -20,15 +20,14 @@ """Text files that will have their doctests tested.""" text_files = [ - 'docs/htmlfill.txt', - 'docs/Validator.txt', - 'formencode/tests/non_empty.txt', - ] + "docs/htmlfill.txt", + "docs/Validator.txt", + "tests/non_empty.txt", +] """Used to resolve text files to absolute paths.""" -base = os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__)))) +base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if six.text_type is str: # Python 3 @@ -36,32 +35,39 @@ OutputChecker = doctest.OutputChecker class OutputChecker3(OutputChecker): - def check_output(self, want, got, optionflags): if want.startswith("u'"): want = want[1:] - elif want.startswith('set(['): - want = want[3:].replace( - '([', '{').replace('])', '}').replace('{}', 'set()') + elif want.startswith("set(["): + want = ( + want[3:] + .replace("([", "{") + .replace("])", "}") + .replace("{}", "set()") + ) return OutputChecker.check_output(self, want, got, optionflags) doctest.OutputChecker = OutputChecker3 def doctest_file(document, verbose, raise_error): - failure_count, test_count = doctest.testfile(document, - module_relative=False, - optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, - verbose=verbose) + failure_count, test_count = doctest.testfile( + document, + module_relative=False, + optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, + verbose=verbose, + ) if raise_error: assert test_count > 0 assert failure_count == 0 def doctest_module(document, verbose, raise_error): - failure_count, test_count = doctest.testmod(document, - optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, - verbose=verbose) + failure_count, test_count = doctest.testmod( + document, + optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, + verbose=verbose, + ) if raise_error: assert test_count > 0 assert failure_count == 0 @@ -69,11 +75,14 @@ def doctest_module(document, verbose, raise_error): def set_func_description(fn, description): """Wrap function and set description attr for pytest to display.""" + def _wrapper(*a_test_args): fn(*a_test_args) + _wrapper.description = description return _wrapper + def collect_functions(): verbose = False raise_error = True @@ -82,26 +91,30 @@ def collect_functions(): name = "Doctests for %s" % (document,) if not document.startswith(os.sep): document = os.path.join(base, document) - yield set_func_description(doctest_file, name), document, \ - verbose, raise_error + yield set_func_description( + doctest_file, name + ), document, verbose, raise_error else: name = "Doctests for %s" % (document.__name__,) - yield set_func_description(doctest_module, name), document, \ - verbose, raise_error + yield set_func_description( + doctest_module, name + ), document, verbose, raise_error -@pytest.mark.parametrize("testfn,document,verbose,raise_error", list(collect_functions())) -def test_doctests(testfn,document,verbose,raise_error): +@pytest.mark.parametrize( + "testfn,document,verbose,raise_error", list(collect_functions()) +) +def test_doctests(testfn, document, verbose, raise_error): """Generate each doctest.""" testfn(document, verbose, raise_error) -if __name__ == '__main__': +if __name__ == "__main__": # Call this file directly if you want to test doctests. args = sys.argv[1:] verbose = False - if '-v' in args: - args.remove('-v') + if "-v" in args: + args.remove("-v") verbose = True if not args: args = text_files + modules diff --git a/formencode/tests/test_email.py b/tests/test_email.py similarity index 100% rename from formencode/tests/test_email.py rename to tests/test_email.py diff --git a/formencode/tests/test_htmlfill.py b/tests/test_htmlfill.py similarity index 100% rename from formencode/tests/test_htmlfill.py rename to tests/test_htmlfill.py diff --git a/formencode/tests/test_htmlfill_control.py b/tests/test_htmlfill_control.py similarity index 100% rename from formencode/tests/test_htmlfill_control.py rename to tests/test_htmlfill_control.py diff --git a/formencode/tests/test_htmlgen.py b/tests/test_htmlgen.py similarity index 100% rename from formencode/tests/test_htmlgen.py rename to tests/test_htmlgen.py diff --git a/formencode/tests/test_htmlrename.py b/tests/test_htmlrename.py similarity index 100% rename from formencode/tests/test_htmlrename.py rename to tests/test_htmlrename.py diff --git a/formencode/tests/test_i18n.py b/tests/test_i18n.py similarity index 100% rename from formencode/tests/test_i18n.py rename to tests/test_i18n.py diff --git a/formencode/tests/test_schema.py b/tests/test_schema.py similarity index 100% rename from formencode/tests/test_schema.py rename to tests/test_schema.py diff --git a/formencode/tests/test_subclassing.py b/tests/test_subclassing.py similarity index 100% rename from formencode/tests/test_subclassing.py rename to tests/test_subclassing.py diff --git a/formencode/tests/test_subclassing_old.py b/tests/test_subclassing_old.py similarity index 100% rename from formencode/tests/test_subclassing_old.py rename to tests/test_subclassing_old.py diff --git a/formencode/tests/test_validators.py b/tests/test_validators.py similarity index 100% rename from formencode/tests/test_validators.py rename to tests/test_validators.py diff --git a/formencode/tests/test_variabledecode.py b/tests/test_variabledecode.py similarity index 100% rename from formencode/tests/test_variabledecode.py rename to tests/test_variabledecode.py diff --git a/tox.ini b/tox.ini index 73dc6c9f..14545ba4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,12 @@ [tox] -envlist=py27,pypy,py36,py37,py38,py39,py310,pypy3 +envlist=py37,py38,py39,py310,py311,pypy3 [testenv] deps= pytest wheel - py27,pypy: pycountry < 19 - py27,pypy: dnspython < 2.0.0 - py{36,37,38,39,310},pypy3: pycountry dnspython + pycountry + dnspython commands= pip install -e . pytest formencode From da0671f3e126077ed28fa6e3a16c8628d280d804 Mon Sep 17 00:00:00 2001 From: Falibur Date: Fri, 21 Apr 2023 21:05:44 +0200 Subject: [PATCH 08/43] Fix binary of swedish translation (#178) --- .../i18n/sv/LC_MESSAGES/FormEncode.mo | Bin 2145 -> 2605 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo index aa132e37fd29618f4a88111b00f5ef47c9d49b89..5f3abb7706062b535af09da026a5615b6bed7473 100644 GIT binary patch delta 878 zcmYk(O-~a+7zgl&whF8-V5u*O8pnuAg;@$T2y0ZRRBd2s(=sO5(a)o#u7xQ&`-n5FbDO5Rd@xyfEVFss1MwZz7HJOJ#Y-(K=E}rj_)525?z2l zBL6~N_}N}vcu2H}#!U=_P*?s1X5khbgt0y%28W<7EF1N0IEKClN8meX!mp6QKkx)h z^%G^_XyhZ9L;n>#kMrpp8W#Kw^{eZ@VlVX5={SbOe%(M_gvaUzme6q|9-(^}E=Pxv zdvO#EJjCur;1Kcs$Uaq z!kjd-Mp?A|Kyh0(+#1hy8j@Fhi`6krbf@u}E2|wT=bElm(v)@66UrzRd0DKvdQZOK zC}FV&`S~)pi+o|8-7Q*nVf0~GT+SPI!OriWK4E5zTfV1+r+8(fjb%!#tMPWzalLD- z`osyOP}e)E&S&=L1&$}{BH-P!U9VxWnH5(VcZ4Sb9F3|sES7UtM3ZIAS##Q0awN77 z=CH2mC_k_mhP+0{X$W2sPV2wE#pIfCpGSM?sU=_3T*anP>E?FgeJn20^7VS}j)4-> J(yaO|`Ukg5nL7Xg delta 417 zcmXZYKT88a5QgzLcS$swK#&jwEfhVIh>3y{5exr-C zQ(7I?Dz<`+jfDq(1{<}?Gu$lPoPi&%7}DaqGPbWsO~DO_U-AJIO1;WYlEhh8#j zxQ$jnLu+`8v-pVuTZ&QEcM{0?n1 z?^wg%xL)duwz!2h$Ue^D1+L>Wj-%TzEu)8P!GN>p2Ho_H<1{Om8<}wM^}bnYv}*^g GBlQ4m#4POq From 1d99b293eab5705eda335b8d218cbb013bb4999d Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Sun, 5 Nov 2023 12:16:35 +0100 Subject: [PATCH 09/43] Support Python 3.12 (#180) --- .github/workflows/run-tests.yml | 4 ++-- Makefile | 1 + setup.cfg | 1 + src/formencode/__init__.py | 10 ++++++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6635747e..d75f3fc8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,8 +13,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10.0 - 3.10.99', '3.11.0-alpha - 3.11.0'] - os: [ubuntu-18.04, macOS-latest, windows-latest] + python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] + os: [ubuntu-latest, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. diff --git a/Makefile b/Makefile index eb9713c4..0275f9c4 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ init: pip install -e . pip install -r requirements-test.txt + ci: pytest diff --git a/setup.cfg b/setup.cfg index de307aea..0fcb8b19 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index b4d46e7d..162a067f 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -1,6 +1,12 @@ from __future__ import absolute_import # formencode package -from pkg_resources import get_distribution, DistributionNotFound +try: + from importlib.metadata import version, PackageNotFoundError +except ImportError: + from pkg_resources import get_distribution + from pkg_resources import DistributionNotFound as PackageNotFoundError + def version(pkg): + return get_distribution(__name__).version from formencode.api import ( NoDefault, Invalid, Validator, Identity, @@ -13,7 +19,7 @@ from formencode.variabledecode import NestedVariables try: - __version__ = get_distribution(__name__).version + __version__ = version(__name__) except DistributionNotFound: # package is not installed __version__ = 'local-test' From c62e59cee1ec2ecbdcb8e1303d7121b5da45b2ec Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 12:34:40 +0100 Subject: [PATCH 10/43] Some cleanup in package init module --- src/formencode/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index 162a067f..b5b8daf3 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -1,12 +1,15 @@ +"""The formencode package""" + from __future__ import absolute_import -# formencode package + try: from importlib.metadata import version, PackageNotFoundError -except ImportError: +except ImportError: # Python < 3.8 from pkg_resources import get_distribution from pkg_resources import DistributionNotFound as PackageNotFoundError - def version(pkg): - return get_distribution(__name__).version + + def version(distribution_name): + return get_distribution(distribution_name).version from formencode.api import ( NoDefault, Invalid, Validator, Identity, @@ -20,6 +23,5 @@ def version(pkg): try: __version__ = version(__name__) -except DistributionNotFound: - # package is not installed +except DistributionNotFound: # package is not installed __version__ = 'local-test' From d682cac4c457ce780f82978dde4974b428cf2423 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 12:34:55 +0100 Subject: [PATCH 11/43] Add Python 3.12 to tox tests --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 14545ba4..8b5ffd82 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py37,py38,py39,py310,py311,pypy3 +envlist=py37,py38,py39,py310,py311,py312,pypy3 [testenv] deps= From aeb9271c10f3725798506f0d4688919d896226ce Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 12:43:44 +0100 Subject: [PATCH 12/43] Remove now unnecessary future imports and encoding --- docs/conf.py | 6 ++---- examples/WebwareExamples/index.py | 2 -- src/formencode/__init__.py | 2 -- src/formencode/api.py | 1 - src/formencode/compound.py | 2 -- src/formencode/context.py | 1 - src/formencode/declarative.py | 1 - src/formencode/doctest_xml_compare.py | 3 --- src/formencode/fieldstorage.py | 2 +- src/formencode/foreach.py | 1 - src/formencode/htmlfill.py | 1 - src/formencode/htmlfill_schemabuilder.py | 2 -- src/formencode/htmlgen.py | 1 - src/formencode/htmlrename.py | 2 -- src/formencode/national.py | 1 - src/formencode/rewritingparser.py | 2 -- src/formencode/schema.py | 1 - src/formencode/validators.py | 1 - src/formencode/variabledecode.py | 1 - tests/__init__.py | 1 - tests/test_cc.py | 3 --- tests/test_compound.py | 3 --- tests/test_context.py | 1 - tests/test_declarative.py | 2 -- tests/test_doctest_xml_compare.py | 1 - tests/test_doctests.py | 1 - tests/test_email.py | 3 --- tests/test_htmlfill.py | 5 ----- tests/test_htmlfill_control.py | 2 -- tests/test_htmlgen.py | 3 --- tests/test_htmlrename.py | 1 - tests/test_i18n.py | 4 ---- tests/test_schema.py | 2 -- tests/test_subclassing.py | 3 --- tests/test_subclassing_old.py | 3 --- tests/test_validators.py | 4 ---- tests/test_variabledecode.py | 1 - 37 files changed, 3 insertions(+), 73 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9fde57c5..1e4d14de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- -# # FormEncode documentation build configuration file, created by # sphinx-quickstart on Tue Aug 30 2011. # @@ -12,7 +9,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the diff --git a/examples/WebwareExamples/index.py b/examples/WebwareExamples/index.py index c8b1a1ea..9faf7f53 100644 --- a/examples/WebwareExamples/index.py +++ b/examples/WebwareExamples/index.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function from formencode import Invalid, htmlfill, Schema, validators from WebKit.Page import Page diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index b5b8daf3..18ca699b 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -1,7 +1,5 @@ """The formencode package""" -from __future__ import absolute_import - try: from importlib.metadata import version, PackageNotFoundError except ImportError: # Python < 3.8 diff --git a/src/formencode/api.py b/src/formencode/api.py index da321fa2..3dfd5b29 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -1,7 +1,6 @@ """ Core classes for validation. """ -from __future__ import absolute_import from . import declarative import gettext diff --git a/src/formencode/compound.py b/src/formencode/compound.py index 6005f1a3..bf2a0c8e 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -1,8 +1,6 @@ """ Validators for applying validations in sequence. """ -from __future__ import absolute_import - from .api import (FancyValidator, Identity, Invalid, NoDefault, Validator, is_validator) import six diff --git a/src/formencode/context.py b/src/formencode/context.py index 128c9122..d898d4b8 100644 --- a/src/formencode/context.py +++ b/src/formencode/context.py @@ -51,7 +51,6 @@ def do_stuff(): And ``page`` will be set to ``'view'`` only inside that ``with`` block. """ -from __future__ import absolute_import import threading diff --git a/src/formencode/declarative.py b/src/formencode/declarative.py index 2375b3b9..770ce6cc 100644 --- a/src/formencode/declarative.py +++ b/src/formencode/declarative.py @@ -17,7 +17,6 @@ Also, you can define a __classinit__(cls, new_attrs) method, which will be called when the class is created (including subclasses). """ -from __future__ import absolute_import import copy import types diff --git a/src/formencode/doctest_xml_compare.py b/src/formencode/doctest_xml_compare.py index ebac496d..94b490c8 100644 --- a/src/formencode/doctest_xml_compare.py +++ b/src/formencode/doctest_xml_compare.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function - import doctest import xml.etree.ElementTree as ET import six diff --git a/src/formencode/fieldstorage.py b/src/formencode/fieldstorage.py index 40a4d1b6..9894bf7f 100644 --- a/src/formencode/fieldstorage.py +++ b/src/formencode/fieldstorage.py @@ -3,7 +3,7 @@ """ Wrapper class for use with cgi.FieldStorage types for file uploads """ -from __future__ import absolute_import + import cgi diff --git a/src/formencode/foreach.py b/src/formencode/foreach.py index 70abfc2b..1bdc46a1 100644 --- a/src/formencode/foreach.py +++ b/src/formencode/foreach.py @@ -1,7 +1,6 @@ """ Validator for repeating items. """ -from __future__ import absolute_import from .api import NoDefault, Invalid from .compound import CompoundValidator, from_python diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index 90f4beda..ce7bdeab 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -1,7 +1,6 @@ """ Parser for HTML forms, that fills in defaults and errors. See ``render``. """ -from __future__ import absolute_import import re diff --git a/src/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py index 2059ff79..36937a59 100644 --- a/src/formencode/htmlfill_schemabuilder.py +++ b/src/formencode/htmlfill_schemabuilder.py @@ -6,8 +6,6 @@ ``listen`` argument), or call ``parse_schema`` to just parse out a ``Schema`` object. """ -from __future__ import absolute_import - from . import validators from . import schema from . import compound diff --git a/src/formencode/htmlgen.py b/src/formencode/htmlgen.py index 6ab61736..94c56c52 100644 --- a/src/formencode/htmlgen.py +++ b/src/formencode/htmlgen.py @@ -55,7 +55,6 @@ 'return to top' """ -from __future__ import absolute_import import xml.etree.ElementTree as ET import six diff --git a/src/formencode/htmlrename.py b/src/formencode/htmlrename.py index e8a5c38f..d884848e 100644 --- a/src/formencode/htmlrename.py +++ b/src/formencode/htmlrename.py @@ -1,8 +1,6 @@ """ Module to rename form fields """ -from __future__ import absolute_import - from formencode.rewritingparser import RewritingParser __all__ = ['rename', 'add_prefix'] diff --git a/src/formencode/national.py b/src/formencode/national.py index a7a76ed9..8d23a5cf 100644 --- a/src/formencode/national.py +++ b/src/formencode/national.py @@ -1,7 +1,6 @@ """ Country specific validators for use with FormEncode. """ -from __future__ import absolute_import import re from .api import FancyValidator diff --git a/src/formencode/rewritingparser.py b/src/formencode/rewritingparser.py index da852006..0342a6c4 100644 --- a/src/formencode/rewritingparser.py +++ b/src/formencode/rewritingparser.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from six.moves import html_parser import re import six diff --git a/src/formencode/schema.py b/src/formencode/schema.py index f7584971..86fe0fa9 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import warnings from .api import _, is_validator, FancyValidator, Invalid, NoDefault diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 11222878..b5fa6585 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -4,7 +4,6 @@ """ Validator/Converters for use with FormEncode. """ -from __future__ import absolute_import import cgi import locale diff --git a/src/formencode/variabledecode.py b/src/formencode/variabledecode.py index 7f93416c..fc0542a0 100644 --- a/src/formencode/variabledecode.py +++ b/src/formencode/variabledecode.py @@ -19,7 +19,6 @@ and list_char keyword args. For example, to have the GET/POST variables, ``a_1=something`` as a list, you would use a ``list_char='_'``. """ -from __future__ import absolute_import from .api import FancyValidator import six diff --git a/tests/__init__.py b/tests/__init__.py index 24f33978..a380a390 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import sys import os diff --git a/tests/test_cc.py b/tests/test_cc.py index c0e5319b..580f4ae8 100644 --- a/tests/test_cc.py +++ b/tests/test_cc.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest from formencode import Invalid diff --git a/tests/test_compound.py b/tests/test_compound.py index d74f8d61..928163a1 100644 --- a/tests/test_compound.py +++ b/tests/test_compound.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest from formencode import compound, Invalid diff --git a/tests/test_context.py b/tests/test_context.py index 4c2a28c9..6e203f4d 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import pytest from formencode.context import Context, ContextRestoreError diff --git a/tests/test_declarative.py b/tests/test_declarative.py index ffce388a..f985ad6e 100644 --- a/tests/test_declarative.py +++ b/tests/test_declarative.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- import unittest import re diff --git a/tests/test_doctest_xml_compare.py b/tests/test_doctest_xml_compare.py index 316abac1..b6fa5e56 100644 --- a/tests/test_doctest_xml_compare.py +++ b/tests/test_doctest_xml_compare.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import sys import formencode.doctest_xml_compare as dxml diff --git a/tests/test_doctests.py b/tests/test_doctests.py index 6efb38fb..d69e3737 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import os import sys import doctest diff --git a/tests/test_email.py b/tests/test_email.py index f79aea80..0fefb200 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals import unittest diff --git a/tests/test_htmlfill.py b/tests/test_htmlfill.py index ecade0a1..b3eb86ef 100644 --- a/tests/test_htmlfill.py +++ b/tests/test_htmlfill.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import os import re import sys diff --git a/tests/test_htmlfill_control.py b/tests/test_htmlfill_control.py index 4ee4fa6c..e678115f 100644 --- a/tests/test_htmlfill_control.py +++ b/tests/test_htmlfill_control.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from formencode import htmlfill @@ -175,4 +174,3 @@ def test_error_attr_form_alt(): test_error_attr_ignore() test_error_attr_form() test_error_attr_form_alt() - \ No newline at end of file diff --git a/tests/test_htmlgen.py b/tests/test_htmlgen.py index 9dccf5bf..05c7f3a5 100644 --- a/tests/test_htmlgen.py +++ b/tests/test_htmlgen.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function - import doctest from formencode.htmlgen import html diff --git a/tests/test_htmlrename.py b/tests/test_htmlrename.py index 5f93c4a7..86a6f92d 100644 --- a/tests/test_htmlrename.py +++ b/tests/test_htmlrename.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from formencode.htmlrename import rename, add_prefix diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 5998f96b..6f4c8f21 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals - import formencode import six diff --git a/tests/test_schema.py b/tests/test_schema.py index 2ed349a2..a2afba2a 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function import unittest from six.moves.urllib.parse import parse_qsl diff --git a/tests/test_subclassing.py b/tests/test_subclassing.py index 3f620797..6fb23db2 100644 --- a/tests/test_subclassing.py +++ b/tests/test_subclassing.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest from formencode.api import is_validator, FancyValidator, Invalid diff --git a/tests/test_subclassing_old.py b/tests/test_subclassing_old.py index 23b62df9..92b34007 100644 --- a/tests/test_subclassing_old.py +++ b/tests/test_subclassing_old.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest import warnings diff --git a/tests/test_validators.py b/tests/test_validators.py index fd7d186d..cfbf4814 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals - import datetime import unittest import pytest diff --git a/tests/test_variabledecode.py b/tests/test_variabledecode.py index d7b6583b..6fe53f58 100644 --- a/tests/test_variabledecode.py +++ b/tests/test_variabledecode.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import unittest from formencode.variabledecode import variable_decode, variable_encode From f3c89e5f2346bc2b6be4777b932ed71fa970d027 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:46:56 +0100 Subject: [PATCH 13/43] Add logo --- docs/_static/formencode.png | Bin 0 -> 23643 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/_static/formencode.png diff --git a/docs/_static/formencode.png b/docs/_static/formencode.png new file mode 100644 index 0000000000000000000000000000000000000000..6595c523cb69441bceba268252daf2619f8730c8 GIT binary patch literal 23643 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 Yk`oj?F*m8t!n__mG}bMCVE^WS0O3XT`v3p{ literal 0 HcmV?d00001 From 7723798ab7fe555d717c3bbe2e6af0d58b6b486c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:47:34 +0100 Subject: [PATCH 14/43] Add flake8 to tests and fix flake8 issues --- .github/workflows/run-qa.yml | 22 +++++++++ .github/workflows/run-tests.yml | 17 ++++--- Makefile | 11 +++-- setup.cfg | 5 +- src/formencode/api.py | 16 +++---- src/formencode/compound.py | 4 +- src/formencode/declarative.py | 2 +- src/formencode/exc.py | 2 +- src/formencode/fieldstorage.py | 5 -- src/formencode/foreach.py | 3 +- src/formencode/htmlfill.py | 2 +- src/formencode/htmlfill_schemabuilder.py | 7 ++- src/formencode/htmlgen.py | 1 + src/formencode/national.py | 24 ++++------ src/formencode/schema.py | 10 +--- src/formencode/validators.py | 61 +++++++++++------------- tests/test_email.py | 8 ++-- tests/test_htmlfill.py | 1 + tests/test_htmlfill_control.py | 1 + tests/test_schema.py | 5 +- tests/test_subclassing_old.py | 2 +- tests/test_validators.py | 12 +++-- tox.ini | 7 +++ 23 files changed, 120 insertions(+), 108 deletions(-) create mode 100644 .github/workflows/run-qa.yml diff --git a/.github/workflows/run-qa.yml b/.github/workflows/run-qa.yml new file mode 100644 index 00000000..222b7cfe --- /dev/null +++ b/.github/workflows/run-qa.yml @@ -0,0 +1,22 @@ +name: Code quality + +on: + push: + branches: + - main + pull_request: {} + +jobs: + build: + runs-on: ubutu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install dependencies + run: make + - name: Run code quality checks + run: make flake8 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d75f3fc8..13d8d2ab 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,25 +13,24 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. - - python-version: pypy-3.7 + - python-version: 'pypy-3.9' os: ubuntu-latest - - python-version: pypy-3.8 + - python-version: 'pypy-3.10' os: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Check out code + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - make + run: make - name: Run tests - run: | - make ci + run: make ci diff --git a/Makefile b/Makefile index 0275f9c4..707329a9 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,26 @@ -.PHONY: docs init: pip install -e . pip install -r requirements-test.txt -ci: +.PHONY: tests + +tests: pytest flake8: - flake8 --ignore=E501,F401,E128,E402,E731,F821 src/formencode + flake8 src tests coverage: pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode publish: - pip install 'twine>=1.5.0' build + pip install "twine>=4" build python -m build twine upload dist/* rm -fr build dist .egg src/FormEncode.egg-info +.PHONY: docs + docs: cd docs && make html @echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m" diff --git a/setup.cfg b/setup.cfg index 0fcb8b19..7a0fe578 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ include_package_data = True where = src [options.extras_require] -testing = +testing = pytest dnspython pycountry @@ -73,4 +73,5 @@ max_line_length=88 [flake8] max-line-length = 88 -extend-ignore = E203 +extend-ignore = E203,E128,E402,E501,E731,F821,F401 + diff --git a/src/formencode/api.py b/src/formencode/api.py index 3dfd5b29..fcb2d441 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -63,6 +63,7 @@ def set_stdtranslation(domain="FormEncode", languages=None, except AttributeError: # Python 3 _stdtrans = t.gettext + set_stdtranslation() # Dummy i18n translation function, nothing is translated here. @@ -195,9 +196,7 @@ def unpack_errors(self, encode_variables=False, dict_char='.', return self.msg -############################################################ -## Base Classes -############################################################ +# Base Classes class Validator(declarative.Declarative): @@ -239,8 +238,9 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): return value - _message_vars_decode = None - if six.text_type is not str: + if six.text_type is str: + _message_vars_decode = None + else: def _message_vars_decode(self, message_vars): """ Under python2, a form value in web frameworks may be encoded as @@ -258,7 +258,7 @@ def _message_vars_decode(self, message_vars): if isinstance(v, six.text_type): try: v2 = v.encode('utf-8') - except Exception as e: + except Exception: v2 = v if v == v2: message_vars[k] = v2 @@ -355,6 +355,7 @@ class _Identity(Validator): def __repr__(self): return 'validators.Identity' + Identity = _Identity() @@ -481,8 +482,7 @@ def __classinit__(cls, new_attrs): stacklevel=cls._inheritance_level + 2) setattr(cls, new, new_attrs[old]) elif new in new_attrs: - setattr(cls, old, deprecated(old=old, new=new)( - new_attrs[new])) + setattr(cls, old, deprecated(old=old, new=new)(new_attrs[new])) def to_python(self, value, state=None): try: diff --git a/src/formencode/compound.py b/src/formencode/compound.py index bf2a0c8e..72bc6dfd 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -7,10 +7,8 @@ __all__ = ['CompoundValidator', 'Any', 'All', 'Pipe'] -############################################################ -## Compound Validators -############################################################ +# Compound Validators def to_python(validator, value, state): return validator.to_python(value, state) diff --git a/src/formencode/declarative.py b/src/formencode/declarative.py index 770ce6cc..c4a7000d 100644 --- a/src/formencode/declarative.py +++ b/src/formencode/declarative.py @@ -189,7 +189,7 @@ def __sourcerepr__(self, source, binding=None): else: break args.extend('%s=%s' % (name, source.makeRepr(value)) - for (name, value) in six.iteritems(vals)) + for (name, value) in six.iteritems(vals)) return '%s(%s)' % (self.__class__.__name__, ', '.join(args)) diff --git a/src/formencode/exc.py b/src/formencode/exc.py index 8bc75298..3abdaf4a 100644 --- a/src/formencode/exc.py +++ b/src/formencode/exc.py @@ -2,7 +2,7 @@ Custom exceptions and warnings. """ -__all__ = ['FERunTimeWarning'] +__all__ = ['FERuntimeWarning'] class FERuntimeWarning(RuntimeWarning): diff --git a/src/formencode/fieldstorage.py b/src/formencode/fieldstorage.py index 9894bf7f..93b2ccd8 100644 --- a/src/formencode/fieldstorage.py +++ b/src/formencode/fieldstorage.py @@ -1,12 +1,7 @@ -## FormEncode, a Form processor -## Copyright (C) 2003, Ian Bicking """ Wrapper class for use with cgi.FieldStorage types for file uploads """ -import cgi - - def convert_fieldstorage(fs): return fs if fs.filename else None diff --git a/src/formencode/foreach.py b/src/formencode/foreach.py index 1bdc46a1..f3b45886 100644 --- a/src/formencode/foreach.py +++ b/src/formencode/foreach.py @@ -136,6 +136,5 @@ def _convert_to_list(self, value): for _n in value: break return value - ## @@: Should this catch any other errors?: - except TypeError: + except TypeError: # @@: Should this catch any other errors? return [value] diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index ce7bdeab..5c919d93 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -280,7 +280,7 @@ def str_compare(self, str1, str2): str1 = six.text_type(str1) else: str1 = str(str1) - if type(str1) == type(str2): + if type(str1) is type(str2): return str1 == str2 if isinstance(str1, six.text_type): str1 = str1.encode(self.encoding or self.default_encoding) diff --git a/src/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py index 36937a59..03f7ade9 100644 --- a/src/formencode/htmlfill_schemabuilder.py +++ b/src/formencode/htmlfill_schemabuilder.py @@ -27,9 +27,8 @@ def parse_schema(form): return listener.schema() -default_validators = dict( - [(name.lower(), getattr(validators, name)) - for name in dir(validators)]) +default_validators = { + name.lower(): getattr(validators, name) for name in dir(validators)} def get_messages(cls, message): @@ -95,7 +94,7 @@ def listen_input(self, parser, tag, attrs): if required: v.validators.append( validators.NotEmpty( - messages=get_messages(validators.NotEmpty, message))) + messages=get_messages(validators.NotEmpty, message))) else: v.validators[0].if_missing = False if add_to_end: diff --git a/src/formencode/htmlgen.py b/src/formencode/htmlgen.py index 94c56c52..4e0090a0 100644 --- a/src/formencode/htmlgen.py +++ b/src/formencode/htmlgen.py @@ -113,6 +113,7 @@ def str(self, arg, encoding=None): arg = arg.encode(default_encoding) return arg + html = _HTML() diff --git a/src/formencode/national.py b/src/formencode/national.py index 8d23a5cf..8c42fb1b 100644 --- a/src/formencode/national.py +++ b/src/formencode/national.py @@ -24,9 +24,7 @@ no_country = ('Please easy_install pycountry or validators handling' ' country names and/or languages will not work.') -############################################################ -## country lists and functions -############################################################ +# country lists and functions country_additions = [ ('BY', _('Belarus')), @@ -141,9 +139,7 @@ def get_language(code): return _l(pycountry.languages.get(alpha_2=code).name) -############################################################ -## country, state and postal code validators -############################################################ +# country, state and postal code validators class DelimitedDigitsPostalCode(Regex): """ @@ -193,8 +189,7 @@ class DelimitedDigitsPostalCode(Regex): def assembly_formatstring(self, partition_lengths, delimiter): if len(partition_lengths) == 1: return _('%d digits') % partition_lengths[0] - else: - return delimiter.join('n' * l for l in partition_lengths) + return delimiter.join('n' * n for n in partition_lengths) def assembly_grouping(self, partition_lengths, delimiter): digit_groups = ['%s' * length for length in partition_lengths] @@ -404,8 +399,8 @@ def _convert_to_python(self, value, state): value, state) value = match.group(1).upper() if not value.startswith('BFPO'): - value = value.replace(' ', '') - value = '%s %s' % (value[:-3], value[-3:]) + value = ''.join(value.split()) + value = '%s %s' % (value[:-3], value[-3:]) return value @@ -616,9 +611,7 @@ def _convert_to_python(self, value, state): return str(value).strip().upper() -############################################################ -## phone number validators -############################################################ +# phone number validators class USPhoneNumber(FancyValidator): """ @@ -801,9 +794,7 @@ def _convert_to_python(self, value, state): return value -############################################################ -## language validators -############################################################ +# language validators class LanguageValidator(FancyValidator): """ @@ -871,4 +862,5 @@ def validators(): return [name for name, value in list(globals().items()) if isinstance(value, type) and issubclass(value, FancyValidator)] + __all__ = ['Invalid'] + validators() diff --git a/src/formencode/schema.py b/src/formencode/schema.py index 86fe0fa9..362c9c07 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -248,9 +248,7 @@ def _convert_from_python(self, value_dict, state): previous_full_dict = getattr(state, 'full_dict', None) state.full_dict = value_dict try: - __traceback_info__ = None for name, value in six.iteritems(value_dict): - __traceback_info__ = 'for_python in %s' % name try: unused.remove(name) except ValueError: @@ -267,8 +265,6 @@ def _convert_from_python(self, value_dict, state): except Invalid as e: errors[name] = e - del __traceback_info__ - for name in unused: validator = self.fields[name] if state is not None: @@ -289,7 +285,6 @@ def _convert_from_python(self, value_dict, state): state.key = previous_key for validator in pre: - __traceback_info__ = 'for_python pre_validator %s' % validator new = validator.from_python(new, state) return new @@ -334,7 +329,7 @@ def subvalidators(self): return result def is_empty(self, value): - ## Generally nothing is empty for us + # generally nothing is empty for us return False def empty_value(self, value): @@ -350,8 +345,7 @@ def _value_is_iterator(self, value): for _v in value: break return True - ## @@: Should this catch any other errors?: - except TypeError: + except TypeError: # @@: should this catch any other errors? return False diff --git a/src/formencode/validators.py b/src/formencode/validators.py index b5fa6585..01d853e3 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1,5 +1,5 @@ -## FormEncode, a Form processor -## Copyright (C) 2003, Ian Bicking +# FormEncode, a Form processor +# Copyright (C) 2003, Ian Bicking """ Validator/Converters for use with FormEncode. @@ -36,18 +36,16 @@ assert Identity and Invalid and NoDefault # silence unused import warnings # Dummy i18n translation function, nothing is translated here. -# Instead this is actually done in api.message. +# Instead, this is actually done in api.message. # The surrounding _('string') of the strings is only for extracting # the strings automatically. # If you run pygettext with this source comment this function out temporarily. _ = lambda s: s -############################################################ -## Utility methods -############################################################ - +# Utility methods # These all deal with accepting both datetime and mxDateTime modules and types + datetime_module = None mxDateTime_module = None @@ -98,9 +96,7 @@ def datetime_isotime(module): return module.ISO.Time -############################################################ -## Wrapper Validators -############################################################ +# Wrapper Validators class ConfirmType(FancyValidator): """ @@ -313,9 +309,7 @@ def _convert_to_python(self, value, state): _convert_from_python = _convert_to_python -############################################################ -## Normal validators -############################################################ +# Normal validators class MaxLength(FancyValidator): """ @@ -1997,19 +1991,19 @@ class DateConverter(FancyValidator): _date_re = dict( dmy=re.compile( r'^\s*(\d\d?)[\-\./\\](\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), mdy=re.compile( r'^\s*(\d\d?|%s)[\-\./\\](\d\d?)[\-\./\\](\d\d\d?\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), ymd=re.compile( r'^\s*(\d\d\d?\d?)[\-\./\\](\d\d?|%s)[\-\./\\](\d\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), my=re.compile( r'^\s*(\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), ym=re.compile( r'^\s*(\d\d\d?\d?)[\-\./\\](\d\d?|%s)\s*$' - % '|'.join(_month_names), re.I)) + % '|'.join(_month_names), re.I)) _formats = dict(d='%d', m='%m', y='%Y') @@ -2333,8 +2327,6 @@ def _convert_from_python(self, value, state): return '%i:%02i%s' % (hour, minute, ampm) - - class ISODateTimeConverter(FancyValidator): """ Converts fields which contain both date and time, in the @@ -2375,7 +2367,6 @@ def _convert_from_python(self, value, state): return value.isoformat("T") - def PostalCode(*kw, **kwargs): deprecation_warning("please use formencode.national.USPostalCode") from formencode.national import USPostalCode @@ -2462,6 +2453,7 @@ def _convert_to_python(self, value, state): def _convert_from_python(self, value, state): return (self.true_values if value else self.false_values)[0] + # Should deprecate: StringBoolean = StringBool @@ -2764,11 +2756,13 @@ def _convert_to_python(self, value_dict, state): raise Invalid( _('You must give a value for %s') % self.required, value_dict, state, - error_dict={self.required: - Invalid(self.message('empty', state), + error_dict={ + self.required: Invalid( + self.message('empty', state), value_dict.get(self.required), state)}) return value_dict + RequireIfPresent = RequireIfMissing @@ -3075,15 +3069,17 @@ def _validateReturn(self, field_dict, state): dt_mod, next_month_year, next_month, 1) assert expires_date > today except ValueError: - return {self.cc_expires_month_field: - self.message('notANumber', state), - self.cc_expires_year_field: - self.message('notANumber', state)} + return { + self.cc_expires_month_field: + self.message('notANumber', state), + self.cc_expires_year_field: + self.message('notANumber', state)} except AssertionError: - return {self.cc_expires_month_field: - self.message('invalidNumber', state), - self.cc_expires_year_field: - self.message('invalidNumber', state)} + return { + self.cc_expires_month_field: + self.message('invalidNumber', state), + self.cc_expires_year_field: + self.message('invalidNumber', state)} class CreditCardSecurityCode(FormValidator): @@ -3150,6 +3146,7 @@ def _validateReturn(self, field_dict, state): def validators(): """Return the names of all validators in this module.""" return [name for name, value in six.iteritems(globals()) - if isinstance(value, type) and issubclass(value, Validator)] + if isinstance(value, type) and issubclass(value, Validator)] + __all__ = ['Invalid'] + validators() diff --git a/tests/test_email.py b/tests/test_email.py index 0fefb200..4f9873c1 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -53,10 +53,10 @@ def test_valid_email_addresses(self): ('foo{bar}@example.com', 'foo{bar}@example.com'), # examples from RFC 3696 # punting on the difficult and extremely uncommon ones - #('"Abc\@def"@example.com', '"Abc\@def"@example.com'), - #('"Fred Bloggs"@example.com', '"Fred Bloggs"@example.com'), - #('"Joe\\Blow"@example.com', '"Joe\\Blow"@example.com'), - #('"Abc@def"@example.com', '"Abc@def"@example.com'), + # ('"Abc\@def"@example.com', '"Abc\@def"@example.com'), + # ('"Fred Bloggs"@example.com', '"Fred Bloggs"@example.com'), + # ('"Joe\\Blow"@example.com', '"Joe\\Blow"@example.com'), + # ('"Abc@def"@example.com', '"Abc@def"@example.com'), ('customer/department=shipping@example.com', 'customer/department=shipping@example.com'), ('$A12345@example.com', '$A12345@example.com'), diff --git a/tests/test_htmlfill.py b/tests/test_htmlfill.py index b3eb86ef..afa624b0 100644 --- a/tests/test_htmlfill.py +++ b/tests/test_htmlfill.py @@ -29,6 +29,7 @@ def collect_values(): fn = os.path.join(data_dir, fn) yield fn + @pytest.mark.parametrize('filename', list(collect_values())) def test_runfile(filename): f = open(filename) diff --git a/tests/test_htmlfill_control.py b/tests/test_htmlfill_control.py index e678115f..e6959594 100644 --- a/tests/test_htmlfill_control.py +++ b/tests/test_htmlfill_control.py @@ -94,6 +94,7 @@ def test_error_attr_ignore(): rendered_html = htmlfill.render(html, errors={"foo": "bang"}, force_defaults=True, data_formencode_form="b",) + assert expected_html == rendered_html def test_error_attr_form(): diff --git a/tests/test_schema.py b/tests/test_schema.py index a2afba2a..9188f4c8 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -176,8 +176,9 @@ class ChainedTest(Schema): b = validators.String() b_confirm = validators.String() - chained_validators = [validators.FieldsMatch('a', 'a_confirm'), - validators.FieldsMatch('b', 'b_confirm')] + chained_validators = [ + validators.FieldsMatch('a', 'a_confirm'), + validators.FieldsMatch('b', 'b_confirm')] def test_multiple_chained_validators_errors(): diff --git a/tests/test_subclassing_old.py b/tests/test_subclassing_old.py index 92b34007..54051c24 100644 --- a/tests/test_subclassing_old.py +++ b/tests/test_subclassing_old.py @@ -239,7 +239,7 @@ def test_is_validator(self): self.assertTrue(is_validator(self.validator)) def test_to_python(self): - with warnings.catch_warnings(record=True) as _ignore: + with warnings.catch_warnings(record=True): ccv = self.validator self.assertEqual(ccv.to_python('2'), 2) self.assertEqual(ccv.to_python('4'), 4) diff --git a/tests/test_validators.py b/tests/test_validators.py index cfbf4814..02f02b8f 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -705,13 +705,14 @@ def test_leading_zeros_allowed(self): validate = self.validator(leading_zeros=True).to_python try: validate('1.2.3.037') - except Invalid as e: + except Invalid: self.fail('IP address octets with leading zeros should be valid') try: validate('1.2.3.0377') except Invalid as e: - self.assertTrue("The octets must be within the range of 0-255" - " (not '377')" in unicode(e).replace("u'", "'")) + self.assertTrue( + "The octets must be within the range of 0-255 (not '377')" + in unicode(e).replace("u'", "'")) else: self.fail( 'IP address octets should not be interpreted as octal numbers') @@ -723,8 +724,9 @@ def setUp(self): self.validator = validators.URL() def test_cojp(self): - self.assertEqual(self.validator.to_python('http://domain.co.jp'), - 'http://domain.co.jp') + self.assertEqual( + self.validator.to_python('http://domain.co.jp'), + 'http://domain.co.jp') def test_1char_thirdlevel(self): self.assertEqual(self.validator.to_python( diff --git a/tox.ini b/tox.ini index 8b5ffd82..d578ee9c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,13 @@ [tox] envlist=py37,py38,py39,py310,py311,py312,pypy3 +[testenv:flake8] +basepython = python3.11 +deps = + flake8 +commands = + flake8 src tests + [testenv] deps= pytest From 134b4fc8748ddd0cb01f03f5d2db6b89835ddd6e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:55:54 +0100 Subject: [PATCH 15/43] Fix some more flake8 issues --- setup.cfg | 2 +- src/formencode/__init__.py | 9 ++++++++- src/formencode/api.py | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7a0fe578..75eb5b52 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,5 +73,5 @@ max_line_length=88 [flake8] max-line-length = 88 -extend-ignore = E203,E128,E402,E501,E731,F821,F401 +extend-ignore = E128,E402,E501,E731 diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index 18ca699b..fde4099a 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -19,7 +19,14 @@ def version(distribution_name): from formencode import national from formencode.variabledecode import NestedVariables +__all__ = [ + 'NoDefault', 'Invalid', 'Validator', 'Identity', 'FancyValidator', + 'is_empty', 'is_validator', + 'Schema', 'CompoundValidator', 'Any', 'All', 'Pipe', + 'ForEach', 'validators', 'national', 'NestedVariables', + '__version__'] + try: __version__ = version(__name__) -except DistributionNotFound: # package is not installed +except PackageNotFoundError: # package is not installed __version__ = 'local-test' diff --git a/src/formencode/api.py b/src/formencode/api.py index fcb2d441..88441c7e 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -67,10 +67,11 @@ def set_stdtranslation(domain="FormEncode", languages=None, set_stdtranslation() # Dummy i18n translation function, nothing is translated here. -# Instead this is actually done in api.Validator.message. +# Instead, this is actually done in api.Validator.message. # The surrounding _('string') of the strings is only for extracting # the strings automatically. -# If you run pygettext with this source comment this function out temporarily. +# If you run pygettext with this source, comment this function out temporarily. + _ = lambda s: s From 8b60178ee25e9e00d7f7fecae0c9ca020bc44a2e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:58:11 +0100 Subject: [PATCH 16/43] Fix GitHub actions --- .github/workflows/run-qa.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-qa.yml b/.github/workflows/run-qa.yml index 222b7cfe..25879ccd 100644 --- a/.github/workflows/run-qa.yml +++ b/.github/workflows/run-qa.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubutu-latest + runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 13d8d2ab..0aaad708 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -33,4 +33,4 @@ jobs: - name: Install dependencies run: make - name: Run tests - run: make ci + run: make tests From 399472d052e176a76483ecf90185e1b6106adbee Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 14:05:42 +0100 Subject: [PATCH 17/43] Fix GitHub action for flake8 --- .github/workflows/run-qa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-qa.yml b/.github/workflows/run-qa.yml index 25879ccd..0946cd2b 100644 --- a/.github/workflows/run-qa.yml +++ b/.github/workflows/run-qa.yml @@ -17,6 +17,6 @@ jobs: with: python-version: '3.12' - name: Install dependencies - run: make + run: pip install flake8 - name: Run code quality checks run: make flake8 From 98e9e97ea6e8a7574fb106d699c2b00e3a6bc5ae Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 16:15:24 +0100 Subject: [PATCH 18/43] Remove now unnecessary dependency on six --- Makefile | 2 +- docs/Validator.txt | 25 ++-- docs/conf.py | 15 +- docs/modules/htmlfill.txt | 1 - examples/WebwareExamples/index.py | 5 +- requirements-dev.txt | 2 +- requirements-test.txt | 4 +- setup.cfg | 1 - src/formencode/api.py | 77 ++--------- src/formencode/compound.py | 15 +- src/formencode/context.py | 15 +- src/formencode/declarative.py | 15 +- src/formencode/doctest_xml_compare.py | 13 +- src/formencode/foreach.py | 12 +- src/formencode/htmlfill.py | 26 ++-- src/formencode/htmlfill_schemabuilder.py | 2 +- src/formencode/htmlgen.py | 55 ++------ src/formencode/interfaces.py | 4 +- src/formencode/national.py | 24 ++-- src/formencode/rewritingparser.py | 33 ++--- src/formencode/schema.py | 59 +++----- src/formencode/validators.py | 169 ++++++++--------------- src/formencode/variabledecode.py | 12 +- tests/__init__.py | 2 +- tests/test_cc.py | 4 +- tests/test_compound.py | 12 +- tests/test_declarative.py | 4 +- tests/test_doctests.py | 31 ++--- tests/test_email.py | 3 +- tests/test_htmlfill.py | 15 +- tests/test_htmlgen.py | 12 +- tests/test_i18n.py | 11 +- tests/test_schema.py | 22 ++- tests/test_subclassing.py | 33 ++--- tests/test_subclassing_old.py | 43 ++---- tests/test_validators.py | 162 ++++++++++------------ tox.ini | 12 +- 37 files changed, 358 insertions(+), 594 deletions(-) diff --git a/Makefile b/Makefile index 707329a9..b27f4fd5 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ init: .PHONY: tests tests: - pytest + pytest tests flake8: flake8 src tests diff --git a/docs/Validator.txt b/docs/Validator.txt index 22c0984f..5166fdcd 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -56,25 +56,22 @@ The core of this validation process is two methods and an exception:: exception. Typically we'd catch that exception, and use it for some sort of feedback. Like: -.. comment (fake raw_input): +.. comment (fake input): - >>> raw_input_input = [] - >>> def raw_input(prompt): - ... value = raw_input_input.pop(0) + >>> input_values = [] + >>> def input(prompt): + ... value = input_values.pop(0) ... print ('%s%s' % (prompt, value)) ... return value - >>> raw_input_input.extend(['ten', '10']) - >>> raw_input_input.extend(['bob', 'bob@nowhere.com']) - >>> import six - >>> if not six.PY2: # Python 3 - ... input = raw_input + >>> input_values.extend(['ten', '10']) + >>> input_values.extend(['bob', 'bob@nowhere.com']) :: >>> def get_integer(): ... while 1: ... try: - ... value = raw_input('Enter a number: ') + ... value = input('Enter a number: ') ... return validator.to_python(value) ... except formencode.Invalid as e: ... print (e) @@ -90,10 +87,10 @@ We can also generalize this kind of function:: >>> def valid_input(prompt, validator): ... while 1: ... try: - ... value = raw_input(prompt) + ... value = input(prompt) ... return validator.to_python(value) ... except formencode.Invalid as e: - ... print (e) + ... print(e) >>> valid_input('Enter your email: ', validators.Email()) Enter your email: bob An email address must contain a single @ @@ -573,12 +570,12 @@ Optionally you can also add a test of your language to ne = formencode.validators.NotEmpty() [...] def test_de(): - _test_lang("de", u"Bitte einen Wert eingeben") + _test_lang("de", "Bitte einen Wert eingeben") And the test for your language:: def test_(): - _test_lang("", u"") + _test_lang("", "") HTTP/HTML Form Input -------------------- diff --git a/docs/conf.py b/docs/conf.py index 1e4d14de..88cc6407 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,15 +32,12 @@ # The suffix of source filenames. source_suffix = '.txt' -# The encoding of source files. -#source_encoding = 'utf-8-sig' - # The master toctree document. master_doc = 'index' # General information about the project. -project = u'FormEncode' -copyright = u'2008-2012, Ian Bicking and Contributors' +project = 'FormEncode' +copyright = '2008-2023, Ian Bicking and Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -180,8 +177,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'FormEncode.tex', u'FormEncode Documentation', - u'Ian Bicking', 'manual'), + ('index', 'FormEncode.tex', 'FormEncode Documentation', + 'Ian Bicking', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -213,6 +210,6 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'formencode', u'FormEncode Documentation', - [u'Ian Bicking'], 1) + ('index', 'formencode', 'FormEncode Documentation', + ['Ian Bicking'], 1) ] diff --git a/docs/modules/htmlfill.txt b/docs/modules/htmlfill.txt index ad13455c..0cff1dd1 100644 --- a/docs/modules/htmlfill.txt +++ b/docs/modules/htmlfill.txt @@ -13,5 +13,4 @@ Module Contents .. autofunction:: escape_formatter .. autofunction:: escapenl_formatter .. autofunction:: ignore_formatter -.. autofunction:: ignore_formatter .. autoclass:: FillingParser diff --git a/examples/WebwareExamples/index.py b/examples/WebwareExamples/index.py index 9faf7f53..4a3fdd7b 100644 --- a/examples/WebwareExamples/index.py +++ b/examples/WebwareExamples/index.py @@ -1,7 +1,6 @@ from formencode import Invalid, htmlfill, Schema, validators from WebKit.Page import Page -import six page_style = ''' @@ -74,8 +73,8 @@ def save(self): try: fields = FormSchema.to_python(fields, self) except Invalid as e: - errors = dict((k, v.encode('utf-8')) - for k, v in six.iteritems(e.unpack_errors())) + errors = {k: v.encode('utf-8') + for k, v in e.unpack_errors().items()} print("Errors:", errors) self.rendered_form = htmlfill.render(form_template, defaults=fields, errors=errors) diff --git a/requirements-dev.txt b/requirements-dev.txt index c40fa96e..7a52c582 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ build wheel -black flake8 +sphinx -r requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt index d858886e..556e04cd 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,5 @@ +dnspython +pycountry pytest pytest-cov wheel -pycountry -dnspython diff --git a/setup.cfg b/setup.cfg index 75eb5b52..d3fd4c90 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,7 +32,6 @@ packages = find: package_dir = =src python_requires = >=3.7 -install_requires = six include_package_data = True [options.packages.find] diff --git a/src/formencode/api.py b/src/formencode/api.py index 88441c7e..1f1cffc8 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -8,7 +8,6 @@ import re import textwrap import warnings -import six try: from pkg_resources import resource_filename @@ -58,10 +57,7 @@ def set_stdtranslation(domain="FormEncode", languages=None, languages=languages, localedir=localedir, fallback=True) global _stdtrans - try: - _stdtrans = t.ugettext - except AttributeError: # Python 3 - _stdtrans = t.gettext + _stdtrans = t.gettext set_stdtranslation() @@ -93,7 +89,7 @@ def inner(*args, **kwargs): return outer -class NoDefault(object): +class NoDefault: """A dummy value used for parameters with no default.""" @@ -150,18 +146,7 @@ def __init__(self, msg, % (self, self.error_list, self.error_dict)) def __str__(self): - val = self.msg - return val - - if six.text_type is not str: # Python 2 - - def __unicode__(self): - if isinstance(self.msg, six.text_type): - return self.msg - elif isinstance(self.msg, str): - return self.msg.decode('utf8') - else: - return six.text_type(self.msg) + return self.msg def unpack_errors(self, encode_variables=False, dict_char='.', list_char='-'): @@ -180,9 +165,9 @@ def unpack_errors(self, encode_variables=False, dict_char='.', for item in self.error_list] if self.error_dict: result = {} - for name, item in six.iteritems(self.error_dict): + for name, item in self.error_dict.items(): result[name] = item if isinstance( - item, six.string_types) else item.unpack_errors() + item, str) else item.unpack_errors() if encode_variables: from . import variabledecode result = variabledecode.variable_encode( @@ -239,32 +224,6 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): return value - if six.text_type is str: - _message_vars_decode = None - else: - def _message_vars_decode(self, message_vars): - """ - Under python2, a form value in web frameworks may be encoded as - UTF8 unicode. The standard error templates use a ``%(value)r`` - string formatting, which will render the error as ``u"Foo"`` instead - of just ``"Foo"``. This decoder, which can be overridden, will - encode the value back to a python string and render ``"Foo"``. This - decoder will only update the error dictionary if the unicode and - string values are equal by comparison. - - v = message_vars.items()[0][1] - """ - if six.text_type is not str: - for (k, v) in message_vars.items(): - if isinstance(v, six.text_type): - try: - v2 = v.encode('utf-8') - except Exception: - v2 = v - if v == v2: - message_vars[k] = v2 - return message_vars - def message(self, msgName, state, **kw): # determine translation function try: @@ -272,8 +231,8 @@ def message(self, msgName, state, **kw): except AttributeError: try: if self.use_builtins_gettext: - import six.moves.builtins - trans = six.moves.builtins._ + import builtins + trans = builtins._ else: trans = _stdtrans except AttributeError: @@ -282,12 +241,7 @@ def message(self, msgName, state, **kw): if not callable(trans): trans = _stdtrans - msg = self._messages[msgName] - msg = trans(msg, **self.gettextargs) - - if self._message_vars_decode: - # handle custom decoding for message vars - kw = self._message_vars_decode(kw) + msg = trans(self._messages[msgName], **self.gettextargs) try: return msg % kw @@ -340,11 +294,10 @@ def _initialize_docstring(cls): This changes the class's docstring to include information about all the messages this validator uses. """ - doc = cls.__doc__ or '' - doc = [textwrap.dedent(doc).rstrip()] - messages = sorted(six.iteritems(cls._messages)) - doc.append('\n\n**Messages**\n\n') - for name, default in messages: + doc = [ + textwrap.dedent(cls.__doc__ or '').rstrip(), + '\n\n**Messages**\n\n'] + for name, default in sorted(cls._messages.items()): default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default) doc.append('``' + name + '``:\n') doc.append(' ' + default + '\n\n') @@ -487,7 +440,7 @@ def __classinit__(cls, new_attrs): def to_python(self, value, state=None): try: - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): value = value.strip() elif hasattr(value, 'mixed'): # Support Paste's MultiDict @@ -515,7 +468,7 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): try: - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): value = value.strip() if not self.accept_python: if self.is_empty(value): @@ -551,7 +504,7 @@ def empty_value(self, value): return None def assert_string(self, value, state): - if not isinstance(value, six.string_types): + if not isinstance(value, str): raise Invalid(self.message('badType', state, type=type(value), value=value), value, state) diff --git a/src/formencode/compound.py b/src/formencode/compound.py index 72bc6dfd..4b3be890 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -1,9 +1,8 @@ """ Validators for applying validations in sequence. """ -from .api import (FancyValidator, Identity, Invalid, NoDefault, Validator, - is_validator) -import six +from .api import ( + FancyValidator, Identity, Invalid, NoDefault, Validator, is_validator) __all__ = ['CompoundValidator', 'Any', 'All', 'Pipe'] @@ -36,14 +35,14 @@ class CompoundValidator(FancyValidator): @staticmethod def __classinit__(cls, new_attrs): FancyValidator.__classinit__(cls, new_attrs) - toAdd = [] - for name, value in six.iteritems(new_attrs): + to_add = [] + for name, value in new_attrs.items(): if is_validator(value) and value is not Identity: - toAdd.append((name, value)) + to_add.append((name, value)) # @@: Should we really delete too? delattr(cls, name) - toAdd.sort() - cls.validators.extend([value for _name, value in toAdd]) + to_add.sort() + cls.validators.extend([value for _name, value in to_add]) def __init__(self, *args, **kw): Validator.__init__(self, *args, **kw) diff --git a/src/formencode/context.py b/src/formencode/context.py index d898d4b8..1f3ac8ea 100644 --- a/src/formencode/context.py +++ b/src/formencode/context.py @@ -25,7 +25,7 @@ def do_stuff(): context object to pass information to another thread. In a single-thread environment it doesn't really matter. -Typically you will create ``Context`` instances for your application, +Typically, you will create ``Context`` instances for your application, environment, etc. These should be global module-level variables, that may be imported by any interested module; each instance is a namespace of its own. @@ -41,10 +41,10 @@ def do_stuff(): value. ``None`` is a typical value for that. Another is ``context.set_default(**vars)``, which will set only those -variables to default values. This will not effect the stack of -scopes, but will only add defaults. +variables to default values. This will not affect the stack of scopes, +but will only add defaults. -When Python 2.5 comes out, this syntax would certainly be useful:: +this syntax would certainly be useful:: with context(page='view'): do stuff... @@ -55,14 +55,13 @@ def do_stuff(): import threading from itertools import count -from six.moves import range __all__ = ['Context', 'ContextRestoreError'] _restore_ids = count() -class NoDefault(object): +class NoDefault: """A dummy value used for parameters with no default.""" @@ -70,7 +69,7 @@ class ContextRestoreError(Exception): """Raised when something is restored out-of-order.""" -class Context(object): +class Context: def __init__(self, default=NoDefault): self.__dict__['_local'] = threading.local() @@ -152,7 +151,7 @@ def __repr__(self): self.__class__.__name__, myid, ' '.join(varlist)) -class RestoreState(object): +class RestoreState: def __init__(self, context, state_id): self.state_id = state_id diff --git a/src/formencode/declarative.py b/src/formencode/declarative.py index c4a7000d..550a2703 100644 --- a/src/formencode/declarative.py +++ b/src/formencode/declarative.py @@ -22,12 +22,9 @@ import types from itertools import count -import six -from six.moves import map -from six.moves import zip -class classinstancemethod(object): +class classinstancemethod: """ Acts like a class method when called from a class, like an instance method when called by an instance. The method should @@ -42,7 +39,7 @@ def __get__(self, obj, cls=None): return _methodwrapper(self.func, obj=obj, cls=cls) -class _methodwrapper(object): +class _methodwrapper: def __init__(self, func, obj, cls): self.func = func @@ -84,7 +81,7 @@ def __new__(meta, class_name, bases, new_attrs): return cls -class singletonmethod(object): +class singletonmethod: """ For Declarative subclasses, this decorator will call the method on the cls.singleton() object if called as a class method (or @@ -100,7 +97,7 @@ def __get__(self, obj, cls=None): return types.MethodType(self.func, obj) -class Declarative(six.with_metaclass(DeclarativeMeta, object)): +class Declarative(metaclass=DeclarativeMeta): __unpackargs__ = () @@ -144,7 +141,7 @@ def __init__(self, *args, **kw): for name in self.__mutableattributes__: if name not in kw: setattr(self, name, copy.copy(getattr(self, name))) - for name, value in six.iteritems(kw): + for name, value in kw.items(): setattr(self, name, value) if 'declarative_count' not in kw: self.declarative_count = next(self.counter) @@ -189,7 +186,7 @@ def __sourcerepr__(self, source, binding=None): else: break args.extend('%s=%s' % (name, source.makeRepr(value)) - for (name, value) in six.iteritems(vals)) + for (name, value) in vals.items()) return '%s(%s)' % (self.__class__.__name__, ', '.join(args)) diff --git a/src/formencode/doctest_xml_compare.py b/src/formencode/doctest_xml_compare.py index 94b490c8..395d697a 100644 --- a/src/formencode/doctest_xml_compare.py +++ b/src/formencode/doctest_xml_compare.py @@ -1,12 +1,7 @@ import doctest import xml.etree.ElementTree as ET -import six -from six.moves import map -from six.moves import zip -try: - XMLParseError = ET.ParseError -except AttributeError: # Python < 2.7 - from xml.parsers.expat import ExpatError as XMLParseError + +XMLParseError = ET.ParseError RealOutputChecker = doctest.OutputChecker @@ -71,7 +66,7 @@ def xml_compare(x1, x2, reporter=None): if reporter: reporter('Tags do not match: %s and %s' % (x1.tag, x2.tag)) return False - for name, value in six.iteritems(x1.attrib): + for name, value in x1.attrib.items(): if x2.attrib.get(name) != value: if reporter: reporter('Attributes do not match: %s=%r, %s=%r' @@ -122,7 +117,7 @@ def make_xml(s): def make_string(xml): - if isinstance(xml, six.string_types): + if isinstance(xml, str): xml = make_xml(xml) s = ET.tostring(xml) if s == '': diff --git a/src/formencode/foreach.py b/src/formencode/foreach.py index f3b45886..96c064a1 100644 --- a/src/formencode/foreach.py +++ b/src/formencode/foreach.py @@ -4,7 +4,6 @@ from .api import NoDefault, Invalid from .compound import CompoundValidator, from_python -import six __all__ = ['ForEach'] @@ -84,7 +83,7 @@ def _attempt_convert(self, value, state, validate): new_list = set(new_list) return new_list else: - raise Invalid('Errors:\n%s' % '\n'.join(six.text_type(e) + raise Invalid('Errors:\n%s' % '\n'.join(str(e) for e in errors if e), value, state, error_list=errors) finally: if state is not None: @@ -106,15 +105,14 @@ def _attempt_convert(self, value, state, validate): def empty_value(self, value): return [] - class _IfMissing(object): + class _IfMissing: def __get__(self, obj, cls=None): if obj is None: return [] - elif obj._if_missing is ForEach._if_missing: + if obj._if_missing is ForEach._if_missing: return [] - else: - return obj._if_missing + return obj._if_missing def __set__(self, obj, value): obj._if_missing = value @@ -126,7 +124,7 @@ def __delete__(self, obj): del _IfMissing def _convert_to_list(self, value): - if isinstance(value, six.string_types): + if isinstance(value, str): return [value] elif value is None: return [] diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index 5c919d93..13b28629 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -5,7 +5,6 @@ import re from formencode.rewritingparser import RewritingParser, html_quote -import six __all__ = ['render', 'htmlliteral', 'default_formatter', 'none_formatter', 'escape_formatter', @@ -125,7 +124,7 @@ def render(form, defaults=None, errors=None, use_all_keys=False, return p.text() -class htmlliteral(object): +class htmlliteral: def __init__(self, html, text=None): if text is None: @@ -246,7 +245,7 @@ def __init__(self, defaults, errors=None, use_all_keys=False, self.in_select = None self.skip_next = False self.errors = errors or {} - if isinstance(self.errors, six.string_types): + if isinstance(self.errors, str): self.errors = {None: self.errors} self.in_error = None self.skip_error = False @@ -275,14 +274,11 @@ def str_compare(self, str1, str2): Compare the two objects as strings (coercing to strings if necessary). Also uses encoding to compare the strings. """ - if not isinstance(str1, six.string_types): - if hasattr(str1, '__unicode__'): - str1 = six.text_type(str1) - else: - str1 = str(str1) + if not isinstance(str1, str): + str1 = str(str1) if type(str1) is type(str2): return str1 == str2 - if isinstance(str1, six.text_type): + if isinstance(str1, str): str1 = str1.encode(self.encoding or self.default_encoding) else: str2 = str2.encode(self.encoding or self.default_encoding) @@ -296,7 +292,7 @@ def close(self): if key in unused_errors: del unused_errors[key] if self.auto_error_formatter: - for key, value in six.iteritems(unused_errors): + for key, value in unused_errors.items(): error_message = self.auto_error_formatter(value) error_message = '\n%s' % (key, error_message) self.insert_at_marker( @@ -319,9 +315,6 @@ def close(self): if self.encoding is not None: new_content = [] for item in self._content: - if (six.text_type is not str # Python 2 - and isinstance(item, str)): - item = item.decode(self.encoding) new_content.append(item) self._content = new_content self._text = self._get_text() @@ -421,11 +414,8 @@ def handle_input(self, attrs, startend): if self.prefix_error: self.write_marker(name) value = self.defaults.get(name) - if (six.text_type is not str # Python 2 - and isinstance(name, six.text_type) and isinstance(value, str)): - value = value.decode(self.encoding or self.default_encoding) if name in self.add_attributes: - for attr_name, attr_value in six.iteritems(self.add_attributes[name]): + for attr_name, attr_value in self.add_attributes[name].items(): if attr_name.startswith('+'): attr_name = attr_name[1:] self.set_attr(attrs, attr_name, @@ -571,7 +561,7 @@ def selected_multiple(self, obj, value): """ if obj is None: return False - if isinstance(obj, six.string_types): + if isinstance(obj, str): return obj == value if hasattr(obj, '__contains__'): if value in obj: diff --git a/src/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py index 03f7ade9..5519a505 100644 --- a/src/formencode/htmlfill_schemabuilder.py +++ b/src/formencode/htmlfill_schemabuilder.py @@ -60,7 +60,7 @@ def force_list(v): return [v] -class SchemaBuilder(object): +class SchemaBuilder: def __init__(self, validators=default_validators): self.validators = validators diff --git a/src/formencode/htmlgen.py b/src/formencode/htmlgen.py index 4e0090a0..c41d526c 100644 --- a/src/formencode/htmlgen.py +++ b/src/formencode/htmlgen.py @@ -57,14 +57,8 @@ """ import xml.etree.ElementTree as ET -import six -from six.moves import map - -try: - from html import escape -except ImportError: # Python < 3.2 - from cgi import escape +from html import escape __all__ = ['html'] @@ -92,26 +86,18 @@ def __call__(self, *args): def quote(self, arg): if arg is None: return '' - if six.text_type is not str: # Python 2 - arg = six.text_type(arg).encode(default_encoding) return escape(arg, True) def str(self, arg, encoding=None): - if isinstance(arg, six.string_types): - if not isinstance(arg, str): - arg = arg.encode(default_encoding) + if isinstance(arg, str): return arg - elif arg is None: + if isinstance(str, bytes): + return arg.encode(default_encoding) + if arg is None: return '' - elif isinstance(arg, (list, tuple)): + if isinstance(arg, (list, tuple)): return ''.join(map(self.str, arg)) - elif isinstance(arg, Element): - return str(arg) - else: - arg = six.text_type(arg) - if not isinstance(arg, str): # Python 2 - arg = arg.encode(default_encoding) - return arg + return str(arg) html = _HTML() @@ -134,7 +120,7 @@ def __call__(self, *args, **kw): if value is None: del kw[name] continue - kw[name] = six.text_type(value) + kw[name] = str(value) if name.endswith('_'): kw[name[:-1]] = value del kw[name] @@ -154,33 +140,22 @@ def __call__(self, *args, **kw): if not ET.iselement(arg): if last is None: if el.text is None: - el.text = six.text_type(arg) + el.text = str(arg) else: - el.text += six.text_type(arg) + el.text += str(arg) else: if last.tail is None: - last.tail = six.text_type(arg) + last.tail = str(arg) else: - last.tail += six.text_type(arg) + last.tail += str(arg) else: last = arg el.append(last) return el - if six.text_type is str: # Python 3 - - def __str__(self): - return ET.tostring( - self, default_encoding).decode(default_encoding) - - else: - - def __str__(self): - return ET.tostring(self, default_encoding) - - def __unicode__(self): - # This is lame! - return str(self).decode(default_encoding) + def __str__(self): + return ET.tostring( + self, default_encoding).decode(default_encoding) def __repr__(self): content = str(self) diff --git a/src/formencode/interfaces.py b/src/formencode/interfaces.py index 2d8cfae4..d110c974 100644 --- a/src/formencode/interfaces.py +++ b/src/formencode/interfaces.py @@ -3,14 +3,14 @@ """ -class Attribute(object): +class Attribute: def __init__(self, description, name=None): self.description = description self.name = name -class Interface(object): +class Interface: pass diff --git a/src/formencode/national.py b/src/formencode/national.py index 8c42fb1b..b78256f0 100644 --- a/src/formencode/national.py +++ b/src/formencode/national.py @@ -6,7 +6,6 @@ from .api import FancyValidator from .compound import Any from .validators import Regex, Invalid, _ -import six try: import pycountry @@ -207,7 +206,7 @@ def assembly_regex(self, partition_lengths, delimiter, strict): def __init__(self, partition_lengths, delimiter=None, strict=False, *args, **kw): - if isinstance(partition_lengths, six.integer_types): + if isinstance(partition_lengths, int): partition_lengths = [partition_lengths] if not delimiter: delimiter = '' @@ -415,19 +414,19 @@ class CountryValidator(FancyValidator): :: >>> CountryValidator.to_python('Germany') - u'DE' + 'DE' >>> CountryValidator.to_python('Finland') - u'FI' + 'FI' >>> CountryValidator.to_python('UNITED STATES') - u'US' + 'US' >>> CountryValidator.to_python('Krakovia') Traceback (most recent call last): ... Invalid: That country is not listed in ISO 3166 >>> CountryValidator.from_python('DE') - u'Germany' + 'Germany' >>> CountryValidator.from_python('FI') - u'Finland' + 'Finland' """ key_ok = True @@ -771,8 +770,7 @@ def _convert_to_python(self, value, state): value = value.encode('ascii', 'strict') except UnicodeEncodeError: raise Invalid(self.message('phoneFormat', state), value, state) - if six.text_type is str: # Python 3 - value = value.decode('ascii') + value = value.decode('ascii') value = self._mark_chars_re.sub('-', value) for f, t in [(' ', ' '), ('--', '-'), (' - ', '-'), ('- ', '-'), (' -', '-')]: @@ -813,17 +811,17 @@ class LanguageValidator(FancyValidator): >>> l = LanguageValidator() >>> l.to_python('German') - u'de' + 'de' >>> l.to_python('Chinese') - u'zh' + 'zh' >>> l.to_python('Klingonian') Traceback (most recent call last): ... Invalid: That language is not listed in ISO 639 >>> l.from_python('de') - u'German' + 'German' >>> l.from_python('zh') - u'Chinese' + 'Chinese' """ key_ok = True diff --git a/src/formencode/rewritingparser.py b/src/formencode/rewritingparser.py index 0342a6c4..ca53ff03 100644 --- a/src/formencode/rewritingparser.py +++ b/src/formencode/rewritingparser.py @@ -1,14 +1,8 @@ -from six.moves import html_parser import re -import six -from six.moves import range -try: - from html import escape -except ImportError: # Python < 3.2 - from cgi import escape - -from six.moves.html_entities import name2codepoint +from html import escape +from html.entities import name2codepoint +from html.parser import HTMLParser def html_quote(v): @@ -16,26 +10,19 @@ def html_quote(v): return '' if hasattr(v, '__html__'): return v.__html__() - if isinstance(v, six.string_types): + if isinstance(v, str): return escape(v, True) - if hasattr(v, '__unicode__'): - v = six.text_type(v) - else: - v = str(v) - return escape(v, True) + return escape(str(v), True) -class RewritingParser(html_parser.HTMLParser): +class RewritingParser(HTMLParser): listener = None skip_next = False def __init__(self): self._content = [] - try: - html_parser.HTMLParser.__init__(self, convert_charrefs=False) - except TypeError: # Python < 3.4 - html_parser.HTMLParser.__init__(self) + HTMLParser.__init__(self, convert_charrefs=False) def feed(self, data): self.data_is_str = isinstance(data, str) @@ -44,7 +31,7 @@ def feed(self, data): self.source_pos = 1, 0 if self.listener: self.listener.reset() - html_parser.HTMLParser.feed(self, data) + HTMLParser.feed(self, data) _entityref_re = re.compile(r'&([a-zA-Z][-.a-zA-Z\d]*);') _charref_re = re.compile(r'&#(\d+|[xX][a-fA-F\d]+);') @@ -60,7 +47,7 @@ def _sub_entityref(self, match): # If we don't recognize it, pass it through as though it # wasn't an entity ref at all return match.group(0) - return six.unichr(name2codepoint[name]) + return chr(name2codepoint[name]) def _sub_charref(self, match): num = match.group(1) @@ -68,7 +55,7 @@ def _sub_charref(self, match): num = int(num[1:], 16) else: num = int(num) - return six.unichr(num) + return chr(num) def handle_misc(self, whatever): self.write_pos() diff --git a/src/formencode/schema.py b/src/formencode/schema.py index 362c9c07..a4cfa089 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -3,9 +3,6 @@ from .api import _, is_validator, FancyValidator, Invalid, NoDefault from . import declarative from .exc import FERuntimeWarning -import six -from six.moves import map -from six.moves import zip __all__ = ['Schema'] @@ -84,7 +81,7 @@ def __classinit__(cls, new_attrs): # Scan through the class variables we've defined *just* # for this subclass, looking for validators (both classes # and instances): - for key, value in six.iteritems(new_attrs): + for key, value in new_attrs.items(): if key in ('pre_validators', 'chained_validators'): if is_validator(value): msg = "Any validator with the name %s will be ignored." % \ @@ -99,12 +96,12 @@ def __classinit__(cls, new_attrs): elif key in cls.fields: del cls.fields[key] - for name, value in six.iteritems(cls.fields): + for name, value in cls.fields.items(): cls.add_field(name, value) def __initargs__(self, new_attrs): self.fields = self.fields.copy() - for key, value in six.iteritems(new_attrs): + for key, value in new_attrs.items(): if key in ('pre_validators', 'chained_validators'): if is_validator(value): msg = "Any validator with the name %s will be ignored." % \ @@ -248,7 +245,7 @@ def _convert_from_python(self, value_dict, state): previous_full_dict = getattr(state, 'full_dict', None) state.full_dict = value_dict try: - for name, value in six.iteritems(value_dict): + for name, value in value_dict.items(): try: unused.remove(name) except ValueError: @@ -325,7 +322,7 @@ def subvalidators(self): result = [] result.extend(self.pre_validators) result.extend(self.chained_validators) - result.extend(six.itervalues(self.fields)) + result.extend(self.fields.values()) return result def is_empty(self, value): @@ -336,9 +333,9 @@ def empty_value(self, value): return {} def _value_is_iterator(self, value): - if isinstance(value, six.string_types + (six.binary_type, )): + if isinstance(value, (bytes, str)): return False - elif isinstance(value, (list, tuple)): + if isinstance(value, (list, tuple)): return True try: @@ -351,26 +348,18 @@ def _value_is_iterator(self, value): def format_compound_error(v, indent=0): if isinstance(v, Exception): - try: - return str(v) - except (UnicodeDecodeError, UnicodeEncodeError): - # There doesn't seem to be a better way to get a str() - # version if possible, and unicode() if necessary, because - # testing for the presence of a __unicode__ method isn't - # enough - return six.text_type(v) - elif isinstance(v, dict): + return str(v) + if isinstance(v, dict): return ('%s\n' % (' ' * indent)).join( '%s: %s' % (k, format_compound_error(value, indent=len(k) + 2)) - for k, value in sorted(six.iteritems(v)) if value is not None) - elif isinstance(v, list): + for k, value in sorted(v.items()) if value is not None) + if isinstance(v, list): return ('%s\n' % (' ' * indent)).join( '%s' % (format_compound_error(value, indent=indent)) for value in v if value is not None) - elif isinstance(v, six.string_types): + if isinstance(v, str): return v - else: - assert False, "I didn't expect something like %s" % repr(v) + raise TypeError("I didn't expect something like %r" % v) def merge_dicts(d1, d2): @@ -380,15 +369,14 @@ def merge_dicts(d1, d2): def merge_values(v1, v2): - if isinstance(v1, six.string_types) and isinstance(v2, six.string_types): + if isinstance(v1, str) and isinstance(v2, str): return v1 + '\n' + v2 - elif isinstance(v1, (list, tuple)) and isinstance(v2, (list, tuple)): + if isinstance(v1, (list, tuple)) and isinstance(v2, (list, tuple)): return merge_lists(v1, v2) - elif isinstance(v1, dict) and isinstance(v2, dict): + if isinstance(v1, dict) and isinstance(v2, dict): return merge_dicts(v1, v2) - else: - # @@: Should we just ignore errors? Seems we do... - return v1 + # @@: Should we just ignore errors? Seems we do... + return v1 def merge_lists(l1, l2): @@ -471,18 +459,15 @@ def to_python(self, value_dict, state): errors = self.func(value_dict, state, self) if not errors: return value_dict - if isinstance(errors, six.string_types): + if isinstance(errors, str): raise Invalid(errors, value_dict, state) - elif isinstance(errors, dict): + if isinstance(errors, dict): raise Invalid( format_compound_error(errors), value_dict, state, error_dict=errors) - elif isinstance(errors, Invalid): + if isinstance(errors, Invalid): raise errors - else: - raise TypeError( - "Invalid error value: %r" % errors) - return value_dict + raise TypeError("Invalid error value: %r" % errors) validate_partial = to_python diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 01d853e3..f6c10eef 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -10,9 +10,6 @@ import re import warnings from encodings import idna -import six -from six.moves import map -from six.moves import range try: # import dnspython import dns.resolver @@ -510,13 +507,13 @@ class Regex(FancyValidator): def __init__(self, *args, **kw): FancyValidator.__init__(self, *args, **kw) - if isinstance(self.regex, six.string_types): + if isinstance(self.regex, str): ops = 0 - assert not isinstance(self.regexOps, six.string_types), ( + assert not isinstance(self.regexOps, str), ( "regexOps should be a list of options from the re module " "(names, or actual values)") for op in self.regexOps: - if isinstance(op, six.string_types): + if isinstance(op, str): ops |= getattr(re, op) else: ops |= op @@ -524,13 +521,13 @@ def __init__(self, *args, **kw): def _validate_python(self, value, state): self.assert_string(value, state) - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): value = value.strip() if not self.regex.search(value): raise Invalid(self.message('invalid', state), value, state) def _convert_to_python(self, value, state): - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): return value.strip() return value @@ -612,7 +609,7 @@ def _validate_python(self, value, state): try: items = '; '.join(map(str, self.list)) except UnicodeError: - items = '; '.join(map(six.text_type, self.list)) + items = '; '.join(map(str, self.list)) raise Invalid( self.message('notIn', state, items=items, value=value), value, state) @@ -687,16 +684,15 @@ def _convert_to_python(self, value, state): state, items=items), value, state) def _convert_from_python(self, value, state): - for k, v in six.iteritems(self.dict): + for k, v in self.dict.items(): if value == v: return k if self.hideDict: raise Invalid(self.message('valueNotFound', state), value, state) - else: - items = '; '.join(map(repr, six.itervalues(self.dict))) - raise Invalid( - self.message('chooseValue', state, - value=repr(value), items=items), value, state) + items = '; '.join(map(repr, self.dict.values())) + raise Invalid( + self.message('chooseValue', state, + value=repr(value), items=items), value, state) class IndexListConverter(FancyValidator): @@ -809,13 +805,6 @@ class DateValidator(FancyValidator): def _validate_python(self, value, state): date_format = self.message('date_format', state) - if (str is not six.text_type # Python 2 - and isinstance(date_format, six.text_type)): - # strftime uses the locale encoding, not Unicode - encoding = locale.getlocale(locale.LC_TIME)[1] or 'utf-8' - date_format = date_format.encode(encoding) - else: - encoding = None if self.earliest_date: if callable(self.earliest_date): earliest_date = self.earliest_date() @@ -823,8 +812,6 @@ def _validate_python(self, value, state): earliest_date = self.earliest_date if value < earliest_date: date_formatted = earliest_date.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('after', state, date=date_formatted), value, state) @@ -835,8 +822,6 @@ def _validate_python(self, value, state): latest_date = self.latest_date if value > latest_date: date_formatted = latest_date.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('before', state, date=date_formatted), value, state) @@ -845,8 +830,6 @@ def _validate_python(self, value, state): now = datetime_now(dt_mod) if value < now: date_formatted = now.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('future', state, date=date_formatted), value, state) @@ -1005,8 +988,6 @@ def _convert_to_python(self, value, state): class ByteString(FancyValidator): """Convert to byte string, treating empty things as the empty string. - Under Python 2.x you can also use the alias `String` for this validator. - Also takes a `max` and `min` argument, and the string length must fall in that range. @@ -1058,27 +1039,27 @@ def __initargs__(self, new_attrs): def _convert_to_python(self, value, state): if value is None: value = '' - elif not isinstance(value, six.string_types): + elif not isinstance(value, str): try: value = bytes(value) except UnicodeEncodeError: - value = six.text_type(value) - if self.encoding is not None and isinstance(value, six.text_type): + value = str(value) + if self.encoding is not None and isinstance(value, str): value = value.encode(self.encoding) return value def _convert_from_python(self, value, state): if value is None: value = '' - elif not isinstance(value, six.string_types): + elif not isinstance(value, str): if isinstance(value, (list, tuple)): value = self.list_joiner.join( self._convert_from_python(v, state) for v in value) try: value = str(value) except UnicodeEncodeError: - value = six.text_type(value) - if self.encoding is not None and isinstance(value, six.text_type): + value = str(value) + if self.encoding is not None and isinstance(value, str): value = value.encode(self.encoding) if self.strip: value = value.strip() @@ -1089,11 +1070,8 @@ def _validate_other(self, value, state): return if value is None: value = '' - elif not isinstance(value, six.string_types): - try: - value = str(value) - except UnicodeEncodeError: - value = six.text_type(value) + elif not isinstance(value, str): + value = str(value) if self.max is not None and len(value) > self.max: raise Invalid( self.message('tooLong', state, max=self.max), value, state) @@ -1110,10 +1088,10 @@ class UnicodeString(ByteString): This is implemented as a specialization of the ByteString class. - Under Python 3.x you can also use the alias `String` for this validator. + You can also use the alias `String` for this validator. In addition to the String arguments, an encoding argument is also - accepted. By default the encoding will be utf-8. You can overwrite + accepted. By default, the encoding will be utf-8. You can overwrite this using the encoding parameter. You can also set inputEncoding and outputEncoding differently. An inputEncoding of None means "do not decode", an outputEncoding of None means "do not encode". @@ -1122,11 +1100,11 @@ class UnicodeString(ByteString): :: - >>> UnicodeString().to_python(None) == u'' + >>> UnicodeString().to_python(None) == '' True - >>> UnicodeString().to_python([]) == u'' + >>> UnicodeString().to_python([]) == '' True - >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') == u'Ni Ni Ni' + >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') == 'Ni Ni Ni' True """ @@ -1145,44 +1123,31 @@ def __init__(self, **kw): def _convert_to_python(self, value, state): if not value: - return u'' - if isinstance(value, six.text_type): + return '' + if isinstance(value, str): return value - if not isinstance(value, six.text_type): - if hasattr(value, '__unicode__'): - value = six.text_type(value) - return value - if not (six.text_type is str # Python 3 - and isinstance(value, bytes) and self.inputEncoding): - value = str(value) - if self.inputEncoding and not isinstance(value, six.text_type): + if isinstance(value, bytes): try: - value = six.text_type(value, self.inputEncoding) + return value.decode( + encoding=self.inputEncoding or self.encoding) except UnicodeDecodeError: raise Invalid(self.message('badEncoding', state), value, state) - except TypeError: - raise Invalid( - self.message('badType', state, - type=type(value), value=value), value, state) - return value + return str(value) def _convert_from_python(self, value, state): - if not isinstance(value, six.text_type): - if hasattr(value, '__unicode__'): - value = six.text_type(value) - else: - value = str(value) - if self.outputEncoding and isinstance(value, six.text_type): + if not isinstance(value, str): + value = str(value) + if self.outputEncoding and isinstance(value, str): value = value.encode(self.outputEncoding) return value def empty_value(self, value): - return u'' + return '' # Provide proper alias for native strings -String = UnicodeString if str is six.text_type else ByteString +String = UnicodeString class Set(FancyValidator): @@ -1350,8 +1315,7 @@ def _validate_python(self, value, state): value, state) try: idna_domain = [idna.ToASCII(p) for p in domain.split('.')] - if six.text_type is str: # Python 3 - idna_domain = [p.decode('ascii') for p in idna_domain] + idna_domain = [p.decode('ascii') for p in idna_domain] idna_domain = '.'.join(idna_domain) except UnicodeError: # UnicodeError: label empty or too long @@ -1453,13 +1417,13 @@ class URL(FancyValidator): You may set allow_idna to False to change this behavior:: >>> URL(allow_idna=True).to_python( - ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') + ... 'http://\u0433\u0443\u0433\u043b.\u0440\u0444') 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=True, add_http=True).to_python( - ... u'\u0433\u0443\u0433\u043b.\u0440\u0444') + ... '\u0433\u0443\u0433\u043b.\u0440\u0444') 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=False).to_python( - ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') + ... 'http://\u0433\u0443\u0433\u043b.\u0440\u0444') Traceback (most recent call last): ... Invalid: That is not a valid URL @@ -1523,7 +1487,7 @@ def _convert_to_python(self, value, state): def _encode_idna(self, url): global urlparse if urlparse is None: - from six.moves.urllib import parse as urlparse + from urllib import parse as urlparse try: scheme, netloc, path, params, query, fragment = urlparse.urlparse( url) @@ -1531,8 +1495,7 @@ def _encode_idna(self, url): return url try: netloc = netloc.encode('idna') - if six.text_type is str: # Python 3 - netloc = netloc.decode('ascii') + netloc = netloc.decode('ascii') return str(urlparse.urlunparse((scheme, netloc, path, params, query, fragment))) except UnicodeError: @@ -1541,9 +1504,9 @@ def _encode_idna(self, url): def _check_url_exists(self, url, state): global http_client, urlparse, socket if http_client is None: - from six.moves import http_client + from http import client as http_client if urlparse is None: - from six.moves.urllib import parse as urlparse + import parse as urlparse if socket is None: import socket scheme, netloc, path, params, query, fragment = urlparse.urlparse( @@ -1562,28 +1525,13 @@ def _check_url_exists(self, url, state): conn.close() except http_client.HTTPException as e: e = str(e) - if str is not six.text_type: # Python 2 - try: - e = e.decode('utf-8') - except UnicodeDecodeError: - try: - e = e.decode('latin-1') - except UnicodeDecodeError: - e = e.decode('ascii', 'replace') raise Invalid( - self.message('httpError', state, error=e), state, url) + self.message('httpError', state, error=str(e)), + state, url) except socket.error as e: - e = str(e) - if str is not six.text_type: # Python 2 - try: - e = e.decode('utf-8') - except UnicodeDecodeError: - try: - e = e.decode('latin-1') - except UnicodeDecodeError: - e = e.decode('ascii', 'replace') raise Invalid( - self.message('socketError', state, error=e), state, url) + self.message('socketError', state, error=str(e)), + state, url) else: if res.status == 404: raise Invalid( @@ -1703,7 +1651,7 @@ def _validate_python(self, value, state=None): is not valid. """ - if not isinstance(value, six.string_types): + if not isinstance(value, str): raise Invalid( self.message('badType', state, type=str(type(value)), value=value), value, state) @@ -1883,7 +1831,7 @@ def _convert_to_python(self, value, state): if isinstance(upload, cgi.FieldStorage): filename = upload.filename content = upload.value - elif isinstance(upload, six.string_types) and upload: + elif isinstance(upload, str) and upload: filename = None # @@: Should this encode upload if it is unicode? content = upload @@ -2300,7 +2248,7 @@ def _to_python_tuple(self, value, state): return hour, minute, second def _convert_from_python(self, value, state): - if isinstance(value, six.string_types): + if isinstance(value, str): return value if hasattr(value, 'hour'): hour, minute = value.hour, value.minute @@ -2438,7 +2386,7 @@ class StringBool(FancyValidator): # originally from TurboGears 1 string=_('Value should be %(true)r or %(false)r')) def _convert_to_python(self, value, state): - if isinstance(value, six.string_types): + if isinstance(value, str): value = value.strip().lower() if value in self.true_values: return True @@ -2871,9 +2819,9 @@ def _validate_python(self, field_dict, state): else: errors[name] = self.message('invalidNoMatch', state) if errors: - error_list = sorted(six.iteritems(errors)) error_message = '
\n'.join( - '%s: %s' % (name, value) for name, value in error_list) + '%s: %s' % (name, value) + for name, value in sorted(errors.items())) raise Invalid(error_message, field_dict, state, error_dict=errors) @@ -2930,10 +2878,9 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join('%s: %s' % (name, value) - for name, value in error_list), + for name, value in sorted(errors.items())), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): @@ -3045,10 +2992,9 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join('%s: %s' % (name, value) - for name, value in error_list), + for name, value in sorted(errors.items())), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): @@ -3121,11 +3067,10 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join( '%s: %s' % (name, value) - for name, value in error_list), + for name, value in errors.items()), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): @@ -3145,7 +3090,7 @@ def _validateReturn(self, field_dict, state): def validators(): """Return the names of all validators in this module.""" - return [name for name, value in six.iteritems(globals()) + return [name for name, value in globals().items() if isinstance(value, type) and issubclass(value, Validator)] diff --git a/src/formencode/variabledecode.py b/src/formencode/variabledecode.py index fc0542a0..4fe9a1ce 100644 --- a/src/formencode/variabledecode.py +++ b/src/formencode/variabledecode.py @@ -21,8 +21,6 @@ """ from .api import FancyValidator -import six -from six.moves import range __all__ = ['variable_decode', 'variable_encode', 'NestedVariables'] @@ -30,7 +28,7 @@ def _sort_key(item): """Robust sort key that sorts items with invalid keys last. - This is used to make sorting behave the same across Python 2 and 3. + This is used to make sorting work with keys that have difference types. """ key = item[0] return not isinstance(key, int), key @@ -41,7 +39,7 @@ def variable_decode(d, dict_char='.', list_char='-'): result = {} dicts_to_sort = set() known_lengths = {} - for key, value in six.iteritems(d): + for key, value in d.items(): keys = key.split(dict_char) new_keys = [] was_repetition_count = False @@ -103,10 +101,10 @@ def variable_decode(d, dict_char='.', list_char='-'): to_sort = to_sort[sub_key] if None in to_sort: none_values = [(0, x) for x in to_sort.pop(None)] - none_values.extend(six.iteritems(to_sort)) + none_values.extend(to_sort.items()) to_sort = none_values else: - to_sort = six.iteritems(to_sort) + to_sort = to_sort.items() to_sort = [x[1] for x in sorted(to_sort, key=_sort_key)] if key in known_lengths: if len(to_sort) < known_lengths[key]: @@ -122,7 +120,7 @@ def variable_encode(d, prepend='', result=None, add_repetitions=True, if result is None: result = {} if isinstance(d, dict): - for key, value in six.iteritems(d): + for key, value in d.items(): if key is None: name = prepend elif not prepend: diff --git a/tests/__init__.py b/tests/__init__.py index a380a390..d452675a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,7 +8,7 @@ # in this case you must set it manually before running the tests). os.environ['LANGUAGE'] = 'C' -# Enable deprecation warnings (disabled by default in Python > 2.6). +# Enable deprecation warnings (which are disabled by default) import warnings warnings.simplefilter('default') diff --git a/tests/test_cc.py b/tests/test_cc.py index 580f4ae8..429ba114 100644 --- a/tests/test_cc.py +++ b/tests/test_cc.py @@ -21,7 +21,7 @@ def message(self, key): def test_validate(self): validate, message = self.validate, self.message - self.assertTrue(validate('visa', '4' + '1' * 15) is None) + self.assertIsNone(validate('visa', '4' + '1' * 15)) self.assertEqual(validate('visa', '5' + '1' * 12), message('invalidNumber')) self.assertEqual(validate('visa', '4' + '1' * 11 + '2'), @@ -49,7 +49,7 @@ def message(self, key): def test_validate(self): validate, message = self.validate, self.message - self.assertTrue(validate('11', '2250') is None) + self.assertIsNone(validate('11', '2250')) self.assertEqual(validate('11', 'test'), message('notANumber')) self.assertEqual(validate('test', '2250'), message('notANumber')) self.assertEqual(validate('10', '2005'), message('invalidNumber')) diff --git a/tests/test_compound.py b/tests/test_compound.py index 928163a1..c8f838ea 100644 --- a/tests/test_compound.py +++ b/tests/test_compound.py @@ -11,8 +11,8 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) - self.assertTrue('validators=[]' in r) + self.assertNotIn('validatorArgs', r) + self.assertIn('validators=[]', r) def test_to_python(self): self.assertRaises(NotImplementedError, @@ -35,7 +35,7 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) + self.assertNotIn('validatorArgs', r) self.assertEqual(r.count('DictConverter'), 2) def test_to_python(self): @@ -58,7 +58,7 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) + self.assertNotIn('validatorArgs', r) self.assertEqual(r.count('DictConverter'), 3) def test_to_python(self): @@ -73,7 +73,7 @@ def test_to_python_error(self): try: self.validator.to_python(3) except Invalid as e: - self.assertTrue('Enter a value from: 2' in str(e)) + self.assertIn('Enter a value from: 2', str(e)) else: self.fail('Invalid should be raised when no validator succeeds.') @@ -90,7 +90,7 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) + self.assertNotIn('validatorArgs', r) self.assertEqual(r.count('DictConverter'), 2) def test_to_python(self): diff --git a/tests/test_declarative.py b/tests/test_declarative.py index f985ad6e..1217621c 100644 --- a/tests/test_declarative.py +++ b/tests/test_declarative.py @@ -22,8 +22,8 @@ def test_call(self): D = declarative.Declarative obj_bar = D(foo='bar', woo='par') obj_baz = obj_bar(foo='baz') - self.assertTrue(type(obj_bar) is type(obj_baz)) - self.assertTrue(obj_bar is not obj_baz) + self.assertIs(type(obj_bar), type(obj_baz)) + self.assertIsNot(obj_bar, obj_baz) self.assertEqual(obj_baz.foo, 'baz') self.assertEqual(obj_baz.woo, 'par') diff --git a/tests/test_doctests.py b/tests/test_doctests.py index d69e3737..4b85dd15 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -8,7 +8,6 @@ from formencode import national from formencode import schema from formencode import validators -import six import pytest @@ -29,24 +28,22 @@ base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -if six.text_type is str: # Python 3 +OutputChecker = doctest.OutputChecker - OutputChecker = doctest.OutputChecker +class OutputChecker3(OutputChecker): + def check_output(self, want, got, optionflags): + if want.startswith("u'"): + want = want[1:] + elif want.startswith("set(["): + want = ( + want[3:] + .replace("([", "{") + .replace("])", "}") + .replace("{}", "set()") + ) + return OutputChecker.check_output(self, want, got, optionflags) - class OutputChecker3(OutputChecker): - def check_output(self, want, got, optionflags): - if want.startswith("u'"): - want = want[1:] - elif want.startswith("set(["): - want = ( - want[3:] - .replace("([", "{") - .replace("])", "}") - .replace("{}", "set()") - ) - return OutputChecker.check_output(self, want, got, optionflags) - - doctest.OutputChecker = OutputChecker3 +doctest.OutputChecker = OutputChecker3 # needed?? def doctest_file(document, verbose, raise_error): diff --git a/tests/test_email.py b/tests/test_email.py index 4f9873c1..ac40b7a6 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -3,7 +3,6 @@ from formencode import Invalid from formencode.validators import Email -import six class TestEmail(unittest.TestCase): @@ -15,7 +14,7 @@ def validate(self, *args): try: return self.validator.to_python(*args) except Invalid as e: - return six.text_type(e) + return str(e) def message(self, message_name, username, domain): email = '@'.join((username, domain)) diff --git a/tests/test_htmlfill.py b/tests/test_htmlfill.py index afa624b0..5e575517 100644 --- a/tests/test_htmlfill.py +++ b/tests/test_htmlfill.py @@ -2,14 +2,11 @@ import re import sys +from html.entities import name2codepoint + import xml.etree.ElementTree as ET -import six -try: - XMLParseError = ET.ParseError -except AttributeError: # Python < 2.7 - from xml.parsers.expat import ExpatError as XMLParseError -from six.moves.html_entities import name2codepoint +XMLParseError = ET.ParseError base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname( os.path.abspath(__file__))))) @@ -46,7 +43,7 @@ def test_runfile(filename): data_content = '' namespace = {} if data_content: - six.exec_(data_content, namespace) + exec(data_content, namespace) data = namespace.copy() data['defaults'] = data.get('defaults', {}) if 'check' in data: @@ -89,7 +86,7 @@ def test_no_trailing_newline(): def test_escape_defaults(): - rarr = six.unichr(name2codepoint['rarr']) + rarr = chr(name2codepoint['rarr']) assert (htmlfill.render('', {}, {}) == '' % rarr) assert (htmlfill.render('', {}, {}) @@ -457,7 +454,7 @@ def test_error_class_textarea(): def test_mix_str_and_unicode(): html = '' - uhtml = six.text_type(html) + uhtml = str(html) cheese = dict(cheese='Käse') ucheese = dict(cheese='Käse') expected = '' diff --git a/tests/test_htmlgen.py b/tests/test_htmlgen.py index 05c7f3a5..0af2421b 100644 --- a/tests/test_htmlgen.py +++ b/tests/test_htmlgen.py @@ -1,11 +1,9 @@ import doctest from formencode.htmlgen import html -import six # A test value that can't be encoded as ascii: -uni_value = u'\xff' -str_value = uni_value if str is six.text_type else uni_value.encode('utf-8') +uni_value = '\xff' def test_basic(): @@ -37,12 +35,12 @@ def test_unicode(): assert False, ( "We need something that can't be ASCII-encoded: %r (%r)" % (uni_value, uni_value.encode('ascii'))) - assert six.text_type(html.b(uni_value)) == u'%s' % uni_value + assert str(html.b(uni_value)) == '%s' % uni_value def test_quote(): assert html.quote('!') == '<hey>!' - assert html.quote(uni_value) == str_value + assert html.quote(uni_value) == uni_value assert html.quote(None) == '' assert html.str(None) == '' assert str(html.b('')) == '<hey>' @@ -58,7 +56,7 @@ def strip(s): return s assert strip(html.comment('test')) == '' - assert strip(html.comment(uni_value)) == '' % str_value + assert strip(html.comment(uni_value)) == '' % uni_value assert strip(html.comment('test')('this')) == '' @@ -76,7 +74,7 @@ def test_namespace(): if __name__ == '__main__': # It's like a super-mini py.test... - for name, value in six.iteritems(globals()): + for name, value in globals().items(): if name.startswith('test'): print(name) value() diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 6f4c8f21..77ad988a 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -1,5 +1,4 @@ import formencode -import six ne = formencode.validators.NotEmpty() @@ -7,15 +6,15 @@ def _test_builtins(func): def dummy(s): return "builtins dummy" - import six.moves.builtins - six.moves.builtins._ = dummy + import builtins + builtins._ = dummy try: ne.to_python("") except formencode.api.Invalid as e: func(e) - del six.moves.builtins._ + del builtins._ def test_builtins(): @@ -34,7 +33,7 @@ def withoutbuiltins(e): def test_state(): - class st(object): + class st: def _(self, s): return "state dummy" @@ -51,7 +50,7 @@ def _test_lang(language, notemptytext): try: ne.to_python("") except formencode.api.Invalid as e: - assert six.text_type(e) == notemptytext + assert str(e) == notemptytext formencode.api.set_stdtranslation() # set back to defaults diff --git a/tests/test_schema.py b/tests/test_schema.py index 9188f4c8..f49cb013 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,6 +1,6 @@ import unittest -from six.moves.urllib.parse import parse_qsl +from urllib.parse import parse_qsl from formencode import Invalid, Validator, compound, foreach, validators from formencode.schema import Schema, merge_dicts, SimpleFormValidator @@ -16,14 +16,14 @@ def _notranslation(s): def setup_module(module): """Disable i18n translation""" - import six.moves.builtins - six.moves.builtins._ = _notranslation + import builtins + builtins._ = _notranslation def teardown_module(module): """Remove translation function""" - import six.moves.builtins - del six.moves.builtins._ + import builtins + del builtins._ def d(**kw): @@ -44,16 +44,15 @@ def cgi_parse(qs): return d -class DecodeCase(object): +class DecodeCase: error_expected = False def __init__(self, schema, input, **output): - self.raw_input = input - self.schema = schema if isinstance(input, str): input = cgi_parse(input) self.input = input + self.schema = schema self.output = output all_cases.append(self) @@ -74,7 +73,7 @@ def __init__(self, *args, **kw): self.output = self.output['text'] def test(self): - print(repr(self.raw_input)) + print(repr(self.input)) try: print(repr(self.schema.to_python(self.input))) except Invalid as e: @@ -211,7 +210,7 @@ def f(value_dict, state, validator): assert f.__doc__ == g.__doc__, "Docstrings don't match!" -class State(object): +class State: pass @@ -261,13 +260,12 @@ def from_python(self, value, state): assert state.key == old_key, "key not restored" -class TestAtLeastOneCheckboxIsChecked(object): +class TestAtLeastOneCheckboxIsChecked: """Tests to address SourceForge bug #1777245 The reporter is trying to enforce agreement to a Terms of Service agreement, with failure to check the 'I agree' checkbox handled as a validation failure. The tests below illustrate a working approach. - """ def setup(self): diff --git a/tests/test_subclassing.py b/tests/test_subclassing.py index 6fb23db2..ec6c2cbd 100644 --- a/tests/test_subclassing.py +++ b/tests/test_subclassing.py @@ -47,23 +47,20 @@ def test_to_python(self): try: cv.to_python('1') except Invalid as e: - self.assertTrue( - 'one is invalid' in str(e), e) + self.assertIn('one is invalid', str(e), e) else: self.fail("one should be invalid") self.assertEqual(cv.to_python('2'), 2) try: cv.to_python('3') except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.to_python('4') except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.to_python('5'), 5) @@ -75,8 +72,7 @@ def test_from_python(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") self.assertEqual(cv.from_python(3), '3') @@ -90,22 +86,19 @@ def test_from_python_no_accept(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") try: cv.from_python(3) except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.from_python(4) except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.from_python(5), '5') @@ -141,8 +134,7 @@ def test_to_python(self): try: nov.to_python('1') except Invalid as e: - self.assertTrue( - 'must not be 1' in str(e), e) + self.assertIn('must not be 1', str(e), e) else: self.fail("1 should be invalid") self.assertEqual(nov.to_python('2'), 2) @@ -156,8 +148,7 @@ def test_to_python_number(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") @@ -172,8 +163,7 @@ def test_to_python_range(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") self.assertEqual(nov.to_python('43'), 43) @@ -241,8 +231,7 @@ def test_to_python(self): try: cav.to_python('4') except Invalid as e: - self.assertTrue( - 'must not be 4' in str(e), e) + self.assertIn('must not be 4', str(e), e) else: self.fail("4 should be invalid") self.assertEqual(cav.to_python('5'), 5) diff --git a/tests/test_subclassing_old.py b/tests/test_subclassing_old.py index 54051c24..f47bd0de 100644 --- a/tests/test_subclassing_old.py +++ b/tests/test_subclassing_old.py @@ -4,8 +4,6 @@ from formencode.api import is_validator, FancyValidator, Invalid from formencode.compound import CompoundValidator, All from formencode.validators import Int -from six.moves import map - with warnings.catch_warnings(record=True) as custom_warnings: warnings.simplefilter('default') @@ -51,7 +49,7 @@ def test_1_warnings(self): output = '\n'.join(map(str, custom_warnings)) for old, new in deprecated: msg = '%s is deprecated; use %s instead' % (old, new) - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedCustomValidator)) @@ -63,23 +61,20 @@ def test_to_python(self): try: cv.to_python('1') except Invalid as e: - self.assertTrue( - 'one is invalid' in str(e), e) + self.assertIn('one is invalid', str(e), e) else: self.fail("one should be invalid") self.assertEqual(cv.to_python('2'), 2) try: cv.to_python('3') except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.to_python('4') except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.to_python('5'), 5) @@ -91,8 +86,7 @@ def test_from_python(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") self.assertEqual(cv.from_python(3), '3') @@ -106,22 +100,19 @@ def test_from_python_no_accept(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") try: cv.from_python(3) except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.from_python(4) except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.from_python(5), '5') @@ -157,7 +148,7 @@ def test_1_warnings(self): # must run first for output in runtime_warnings, not_one_warnings: output = '\n'.join(map(str, output)) msg = '_to_python is deprecated; use _convert_to_python instead' - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedNotOneValidator)) @@ -171,8 +162,7 @@ def test_to_python(self): try: nov.to_python('1') except Invalid as e: - self.assertTrue( - 'must not be 1' in str(e), e) + self.assertIn('must not be 1', str(e), e) else: self.fail("1 should be invalid") self.assertEqual(nov.to_python('2'), 2) @@ -187,8 +177,7 @@ def test_to_python_number(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") @@ -204,8 +193,7 @@ def test_to_python_range(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") self.assertEqual(nov.to_python('43'), 43) @@ -232,7 +220,7 @@ def setUp(self): def test_1_warnings(self): output = '\n'.join(map(str, custom_compound_warnings)) msg = 'attempt_convert is deprecated; use _attempt_convert instead' - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedCustomCompoundValidator)) @@ -280,7 +268,7 @@ def test_1_warnings(self): # must run first for output in runtime_warnings, all_and_not_one_warnings: output = '\n'.join(map(str, output)) msg = 'attempt_convert is deprecated; use _attempt_convert instead' - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedAllAndNotOneValidator)) @@ -295,8 +283,7 @@ def test_to_python(self): try: cav.to_python('4') except Invalid as e: - self.assertTrue( - 'must not be 4' in str(e), e) + self.assertIn('must not be 4', str(e), e) else: self.fail("4 should be invalid") self.assertEqual(cav.to_python('5'), 5) diff --git a/tests/test_validators.py b/tests/test_validators.py index 02f02b8f..5db4e085 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -8,24 +8,18 @@ from formencode.schema import Schema from formencode.foreach import ForEach from formencode.api import NoDefault -import six - -if not six.PY2: - unicode = str def validate(validator, value): try: return validator.to_python(value) - return None except Invalid as e: return e.unpack_errors() def validate_from(validator, value): try: - validator.from_python(value) - return None + return validator.from_python(value) except Invalid as e: return e.unpack_errors() @@ -35,13 +29,13 @@ class TestValidators(unittest.TestCase): def testHelp(self): from pydoc import text, plain s = plain(text.document(validators)) - self.assertTrue('Validator/Converters for use with FormEncode.' in s) - self.assertTrue('class Bool' in s) - self.assertTrue('Always Valid, returns True or False' in s) - self.assertTrue('class Email' in s) - self.assertTrue('Validate an email address.' in s) - self.assertTrue('class FieldsMatch' in s) - self.assertTrue('Tests that the given fields match' in s) + self.assertIn('Validator/Converters for use with FormEncode.', s) + self.assertIn('class Bool', s) + self.assertIn('Always Valid, returns True or False', s) + self.assertIn('class Email', s) + self.assertIn('Validate an email address.', s) + self.assertIn('class FieldsMatch', s) + self.assertIn('Tests that the given fields match', s) class TestByteStringValidator(unittest.TestCase): @@ -51,13 +45,12 @@ def setUp(self): self.messages = self.validator.message def test_alias(self): - if str is not six.text_type: # Python 2 - self.assertTrue(self.validator is validators.String) + self.assertIsNot(self.validator, validators.String) def test_docstring(self): doc = self.validator.__doc__ - self.assertTrue( - 'Enter a value not more than ``%(max)i`` characters long' in doc) + self.assertIn( + 'Enter a value not more than ``%(max)i`` characters long', doc) def test_sv_min(self): sv = self.validator(min=2, accept_python=False) @@ -89,56 +82,55 @@ def setUp(self): self.validator = validators.UnicodeString def test_alias(self): - if str is six.text_type: # Python 3 - self.assertTrue(self.validator is validators.String) + self.assertIs(self.validator, validators.String) def test_docstring(self): doc = self.validator.__doc__ - self.assertTrue('Invalid data or incorrect encoding' in doc) + self.assertIn('Invalid data or incorrect encoding', doc) def test_unicode(self): un = self.validator() self.assertEqual(un.to_python(12), '12') - self.assertTrue(type(un.to_python(12)) is six.text_type) + self.assertIs(type(un.to_python(12)), str) self.assertEqual(un.from_python(12), '12'.encode('ascii')) - self.assertTrue(type(un.from_python(12)) is bytes) + self.assertIs(type(un.from_python(12)), bytes) def test_unicode_encoding(self): uv = self.validator() us = 'käse' u7s, u8s = us.encode('utf-7'), us.encode('utf-8') self.assertEqual(uv.to_python(u8s), us) - self.assertTrue(type(uv.to_python(u8s)) is six.text_type) + self.assertIs(type(uv.to_python(u8s)), str) self.assertEqual(uv.from_python(us), u8s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(encoding='utf-7') self.assertEqual(uv.to_python(u7s), us) - self.assertTrue(type(uv.to_python(u7s)) is six.text_type) + self.assertIs(type(uv.to_python(u7s)), str) self.assertEqual(uv.from_python(us), u7s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(inputEncoding='utf-7') self.assertEqual(uv.to_python(u7s), us) - self.assertTrue(type(uv.to_python(u7s)) is six.text_type) + self.assertIs(type(uv.to_python(u7s)), str) uv = self.validator(outputEncoding='utf-7') self.assertEqual(uv.from_python(us), u7s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(inputEncoding=None) self.assertEqual(uv.to_python(us), us) - self.assertTrue(type(uv.to_python(us)) is six.text_type) + self.assertIs(type(uv.to_python(us)), str) self.assertEqual(uv.from_python(us), u8s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(outputEncoding=None) self.assertEqual(uv.to_python(u8s), us) - self.assertTrue(type(uv.to_python(u8s)) is six.text_type) + self.assertIs(type(uv.to_python(u8s)), str) self.assertEqual(uv.from_python(us), us) - self.assertTrue(type(uv.from_python(us)) is six.text_type) + self.assertIs(type(uv.from_python(us)), str) def test_unicode_empty(self): iv = self.validator() for value in [None, b"", ""]: result = iv.to_python(value) self.assertEqual(result, "") - self.assertTrue(isinstance(result, six.text_type)) + self.assertIsInstance(result, str) class TestIntValidator(unittest.TestCase): @@ -149,7 +141,7 @@ def setUp(self): def test_docstring(self): doc = self.validator.__doc__ - self.assertTrue('Please enter an integer value' in doc) + self.assertIn('Please enter an integer value', doc) def test_int_min(self): iv = self.validator(min=5) @@ -165,8 +157,8 @@ def test_int_max(self): def test_int_minmax_optional(self): iv = self.validator(min=5, max=10, if_empty=None) - self.assertTrue(iv.to_python("") is None) - self.assertTrue(iv.to_python(None) is None) + self.assertIsNone(iv.to_python("")) + self.assertIsNone(iv.to_python(None)) self.assertEqual(iv.to_python('7'), 7) self.assertEqual(validate(iv, "1"), self.messages('tooLow', None, min=5)) @@ -208,15 +200,15 @@ def test_bad_dates(self): try: dc.to_python('20/12/150') except Invalid as e: - self.assertTrue( - 'Please enter a four-digit year after 1899' in unicode(e)) + self.assertIn( + 'Please enter a four-digit year after 1899', str(e)) else: self.fail('Date should be invalid') try: dc.to_python('oh/happy/day') except Invalid as e: - self.assertTrue( - 'Please enter the date in the form DD/MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form DD/MM/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -233,17 +225,16 @@ def test_month_style_alias(self): dc = self.validator(month_style=style) self.assertEqual(dc.month_style, 'ymd') try: - dc = self.validator(month_style='Klingon') + self.validator(month_style='Klingon') except TypeError as e: - self.assertTrue( - unicode(e).replace("u'", "'") == "Bad month_style: 'klingon'") + self.assertEqual(str(e), "Bad month_style: 'klingon'") else: self.fail('month_style should be invalid') try: - dc = self.validator(month_style='ydm') + self.validator(month_style='ydm') except TypeError as e: - self.assertTrue( - unicode(e).replace("u'", "'") == "Bad month_style: 'ydm'") + self.assertEqual( + str(e), "Bad month_style: 'ydm'") else: self.fail('month_style should be invalid') @@ -257,14 +248,14 @@ def test_us_style(self): try: self.assertEqual(dc.to_python('20/12/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('12/Dec/2007'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form MM/DD/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form MM/DD/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -278,14 +269,14 @@ def test_euro_style(self): try: self.assertEqual(dc.to_python('12/20/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('Dec/12/2007'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form DD/MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form DD/MM/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -299,14 +290,14 @@ def test_iso_style(self): try: self.assertEqual(dc.to_python('2013/30/06'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('2013/06/Jun'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form YYYY/MM/DD' in unicode(e)) + self.assertIn( + 'Please enter the date in the form YYYY/MM/DD', str(e)) else: self.fail('Date should be invalid') @@ -320,21 +311,21 @@ def test_no_day(self): try: self.assertEqual(dc.to_python('20/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('12/20/2007'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form MM/YYYY', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('2007/Dec'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form MM/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -384,30 +375,30 @@ def test_time(self): try: tc.to_python('25:30:15') except Invalid as e: - self.assertTrue( - 'You must enter an hour in the range 0-23' in unicode(e)) + self.assertIn( + 'You must enter an hour in the range 0-23', str(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:75:15') except Invalid as e: - self.assertTrue( - 'You must enter a minute in the range 0-59' in unicode(e)) + self.assertIn( + 'You must enter a minute in the range 0-59', str(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:30:75') except Invalid as e: - self.assertTrue( - 'You must enter a second in the range 0-59' in unicode(e)) + self.assertIn( + 'You must enter a second in the range 0-59', str(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:30:zx') except Invalid as e: - self.assertTrue( - 'The second value you gave is not a number' in unicode(e)) - self.assertTrue('zx' in unicode(e)) + self.assertIn( + 'The second value you gave is not a number', str(e)) + self.assertIn('zx', str(e)) else: self.fail('Time should be invalid') @@ -617,8 +608,8 @@ class TestOneOfValidator(unittest.TestCase): def test_docstring(self): doc = validators.OneOf.__doc__ - self.assertTrue( - 'Value must be one of: ``%(items)s`` (not ``%(value)r``)' in doc) + self.assertIn( + 'Value must be one of: ``%(items)s`` (not ``%(value)r``)', doc) def test_unicode_list(self): o = validators.OneOf(['ö', 'a']) @@ -639,34 +630,27 @@ class foo(Schema): self.assertEqual(expected, value) def test_mixed_list(self): - """before `_message_vars_decode`, the errors were: - Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'c') - Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'\xa7') - """ o = validators.OneOf(['a', 'b', 'ö', '™', 1, 2, 3]) try: o.to_python('c') raise ValueError("exception expected") except Invalid as e: - self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'c')" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'c')", str(e)) try: o.to_python(9) raise ValueError("exception expected") except Invalid as e: - self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 9)" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 9)", str(e)) try: - o.to_python(u'd') + o.to_python('d') raise ValueError("exception expected") except Invalid as e: - self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'd')" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'd')", str(e)) try: o.to_python('§') raise ValueError("exception expected") except Invalid as e: - if six.PY2: - self.assertTrue(r"Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'\xa7')" in unicode(e)) - else: - self.assertTrue(r"Value must be one of: a; b; ö; ™; 1; 2; 3 (not '§')" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not '§')", str(e)) class TestIPAddressValidator(unittest.TestCase): @@ -691,13 +675,13 @@ def test_leading_zeros(self): try: validate('1.2.3.037') except Invalid as e: - self.assertTrue('The octets must not have leading zeros' in unicode(e)) + self.assertIn('The octets must not have leading zeros', str(e)) else: self.fail('IP address octets with leading zeros should be invalid') try: validate('1.2.3.0377') except Invalid as e: - self.assertTrue('The octets must not have leading zeros' in unicode(e)) + self.assertIn('The octets must not have leading zeros', str(e)) else: self.fail('IP octets with leading zeros should be invalid') @@ -710,9 +694,9 @@ def test_leading_zeros_allowed(self): try: validate('1.2.3.0377') except Invalid as e: - self.assertTrue( - "The octets must be within the range of 0-255 (not '377')" - in unicode(e).replace("u'", "'")) + self.assertIn( + "The octets must be within the range of 0-255 (not '377')", + str(e)) else: self.fail( 'IP address octets should not be interpreted as octal numbers') diff --git a/tox.ini b/tox.ini index d578ee9c..90c823a5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,12 @@ [tox] -envlist=py37,py38,py39,py310,py311,py312,pypy3 +envlist=flake8,py37,py38,py39,py310,py311,py312,pypy3,docs + +[testenv:docs] +basepython = python3.11 +deps = + sphinx +commands = + sphinx-build -b html -nEW docs docs/_build/html [testenv:flake8] basepython = python3.11 @@ -15,5 +22,4 @@ deps= pycountry dnspython commands= - pip install -e . - pytest formencode + pytest tests {posargs} From 5ab17f3221961700684619c8499615af2020d093 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 16:20:08 +0100 Subject: [PATCH 19/43] Replace deprecated dnspython call --- requirements-test.txt | 2 +- setup.cfg | 2 +- src/formencode/validators.py | 4 ++-- tox.ini | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements-test.txt b/requirements-test.txt index 556e04cd..4582cadb 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ -dnspython +dnspython >= 2 pycountry pytest pytest-cov diff --git a/setup.cfg b/setup.cfg index d3fd4c90..ec6bfe79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,7 +40,7 @@ where = src [options.extras_require] testing = pytest - dnspython + dnspython >= 2 pycountry # Babel configuration diff --git a/src/formencode/validators.py b/src/formencode/validators.py index f6c10eef..3e82be7a 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1335,10 +1335,10 @@ def _validate_python(self, value, state): import socket try: try: - dns.resolver.query(domain, 'MX') + dns.resolver.resolve(domain, 'MX') except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): try: - dns.resolver.query(domain, 'A') + dns.resolver.resolve(domain, 'A') except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): raise Invalid( self.message('domainDoesNotExist', diff --git a/tox.ini b/tox.ini index 90c823a5..f7afdc4c 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,6 @@ deps= pytest wheel pycountry - dnspython + dnspython >= 2 commands= pytest tests {posargs} From d05b7955eb9c766febcf015413bb5842b6e63e36 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 16:31:49 +0100 Subject: [PATCH 20/43] Fix test case setup --- src/formencode/validators.py | 1 - tests/test_schema.py | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 3e82be7a..f1a607fa 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -6,7 +6,6 @@ """ import cgi -import locale import re import warnings from encodings import idna diff --git a/tests/test_schema.py b/tests/test_schema.py index f49cb013..4f57bb38 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -260,7 +260,7 @@ def from_python(self, value, state): assert state.key == old_key, "key not restored" -class TestAtLeastOneCheckboxIsChecked: +class TestAtLeastOneCheckboxIsChecked(unittest.TestCase): """Tests to address SourceForge bug #1777245 The reporter is trying to enforce agreement to a Terms of Service @@ -268,7 +268,7 @@ class TestAtLeastOneCheckboxIsChecked: a validation failure. The tests below illustrate a working approach. """ - def setup(self): + def setUp(self): self.not_empty_messages = {'missing': 'a missing value message'} class CheckForCheckboxSchema(Schema): @@ -279,7 +279,7 @@ class CheckForCheckboxSchema(Schema): def test_Schema_with_input_present(self): # result = self.schema.to_python({'agree': 'yes'}) - assert result['agree'] is True + self.assertTrue(result['agree']) def test_Schema_with_input_missing(self): # @@ -287,10 +287,10 @@ def test_Schema_with_input_missing(self): self.schema.to_python({}) except Invalid as exc: error_message = exc.error_dict['agree'].msg - assert self.not_empty_messages['missing'] == error_message, \ - error_message + self.assertEqual(self.not_empty_messages['missing'], error_message, + error_message) else: - assert False, 'missing input not detected' + self.fail('missing input not detected') class TestStrictSchemaWithMultipleEqualInputFields(unittest.TestCase): From ef7dbc3cea43d8dda55b77935405b3dbb12299e9 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 19:20:34 +0100 Subject: [PATCH 21/43] Avoid the deprecated pkg_resources module --- docs/conf.py | 12 ------- src/formencode/api.py | 55 +++++++++++++------------------ src/formencode/htmlfill.py | 4 +-- src/formencode/rewritingparser.py | 13 ++++---- src/formencode/validators.py | 2 -- tests/__init__.py | 18 +++++++--- tests/test_doctests.py | 4 ++- tox.ini | 3 +- 8 files changed, 50 insertions(+), 61 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 88cc6407..636bf3c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,14 +9,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import os -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) - # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -42,12 +34,8 @@ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -# -# The short X.Y version. -version = '2.0.0' from pkg_resources import get_distribution release = get_distribution('formencode').version -# for example take major/minor version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/src/formencode/api.py b/src/formencode/api.py index 1f1cffc8..88c62b39 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -2,52 +2,43 @@ Core classes for validation. """ -from . import declarative import gettext import os import re import textwrap import warnings -try: - from pkg_resources import resource_filename -except ImportError: - resource_filename = None +from . import declarative __all__ = ['NoDefault', 'Invalid', 'Validator', 'Identity', 'FancyValidator', 'is_empty', 'is_validator'] def get_localedir(): - """Retrieve the location of locales. + """Retrieve the location of locales.""" + file_dir = os.path.join(os.path.dirname(__file__), 'i18n') + if not hasattr(os, 'access'): + # this happens on Google App Engine + return file_dir + + import importlib.resources as resources + # try to get it via the resource + pkg_name = __name__.split('.', 1)[0] + try: + resource_dir = resources.files(pkg_name) / 'i18n' + except AttributeError: # Python < 3.11 + with resources.path(pkg_name, 'i18n') as resource_dir: + pass + if os.access(resource_dir, os.R_OK | os.X_OK): + # if the resource is present, use it + return resource_dir - If we're built as an egg, we need to find the resource within the egg. - Otherwise, we need to look for the locales on the filesystem or in the - system message catalog. + # otherwise, search the filesystem + if os.access(file_dir, os.R_OK | os.X_OK): + return file_dir - """ - locale_dir = '' - # Check the egg first - if resource_filename is not None: - try: - locale_dir = resource_filename(__name__, "i18n") - except NotImplementedError: - # resource_filename doesn't work with non-egg zip files - pass - if not hasattr(os, 'access'): - # This happens on Google App Engine - return os.path.join(os.path.dirname(__file__), 'i18n') - if os.access(locale_dir, os.R_OK | os.X_OK): - # If the resource is present in the egg, use it - return locale_dir - - # Otherwise, search the filesystem - locale_dir = os.path.join(os.path.dirname(__file__), 'i18n') - if not os.access(locale_dir, os.R_OK | os.X_OK): - # Fallback on the system catalog - locale_dir = os.path.normpath('/usr/share/locale') - - return locale_dir + # fallback on the system catalog + return os.path.normpath('/usr/share/locale') def set_stdtranslation(domain="FormEncode", languages=None, diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index 13b28629..a9fd12d9 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -68,8 +68,8 @@ def render(form, defaults=None, errors=None, use_all_keys=False, ``listener`` can be an object that watches fields pass; the only one currently is in ``htmlfill_schemabuilder.SchemaBuilder`` - ``encoding`` specifies an encoding to assume when mixing str and - unicode text in the template. + ``encoding`` specifies an encoding to assume when mixing bytes and + str text in the template. ``prefix_error`` specifies if the HTML created by auto_error_formatter is put before the input control (default) or after the control. diff --git a/src/formencode/rewritingparser.py b/src/formencode/rewritingparser.py index ca53ff03..9d29b797 100644 --- a/src/formencode/rewritingparser.py +++ b/src/formencode/rewritingparser.py @@ -148,13 +148,12 @@ def _get_text(self): except UnicodeDecodeError as e: if self.data_is_str: e.reason += ( - " the form was passed in as an encoded string, but" - " some data or error messages were unicode strings;" - " the form should be passed in as a unicode string") + " the form was passed in as a byte string," + " but some data or error messages were normal strings;" + " the form should be passed in as a string") else: e.reason += ( - " the form was passed in as an unicode string, but" - " some data or error message was an encoded string;" - " the data and error messages should be passed in as" - " unicode strings") + " the form was passed in as a string, but some data" + " or error message was encoded; the data and error" + " messages should be passed in as strings") raise diff --git a/src/formencode/validators.py b/src/formencode/validators.py index f1a607fa..a38ee503 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -841,8 +841,6 @@ def _validate_python(self, value, state): dt_mod, value.year, value.month, value.day) if value_as_date < today: date_formatted = now.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('future', state, date=date_formatted), value, state) diff --git a/tests/__init__.py b/tests/__init__.py index d452675a..922f3c1a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,6 @@ -import sys import os +import sys +import warnings sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) @@ -9,8 +10,17 @@ os.environ['LANGUAGE'] = 'C' # Enable deprecation warnings (which are disabled by default) -import warnings warnings.simplefilter('default') -import pkg_resources -pkg_resources.require('FormEncode') +try: + try: + import importlib.metadata as metadata + try: + metadata.distribution("FormEncode") + except metadata.PackageNotFoundError as error: + raise ImportError from error + except ImportError: # Python < 3.8 + import pkg_resources + pkg_resources.require('FormEncode') +except ImportError as error: + raise ImportError("Install FormEncode before running the tests") from error diff --git a/tests/test_doctests.py b/tests/test_doctests.py index 4b85dd15..5361cace 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -30,6 +30,7 @@ OutputChecker = doctest.OutputChecker + class OutputChecker3(OutputChecker): def check_output(self, want, got, optionflags): if want.startswith("u'"): @@ -43,7 +44,8 @@ def check_output(self, want, got, optionflags): ) return OutputChecker.check_output(self, want, got, optionflags) -doctest.OutputChecker = OutputChecker3 # needed?? + +doctest.OutputChecker = OutputChecker3 def doctest_file(document, verbose, raise_error): diff --git a/tox.ini b/tox.ini index f7afdc4c..64b7f69b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,10 @@ [tox] -envlist=flake8,py37,py38,py39,py310,py311,py312,pypy3,docs +envlist=py3{7,8,9,10,11,12},pypy3,flake8,docs [testenv:docs] basepython = python3.11 deps = + pycountry sphinx commands = sphinx-build -b html -nEW docs docs/_build/html From e27ec28c42791ceac4efdaa550d84c26a22ed5b1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 20:57:12 +0100 Subject: [PATCH 22/43] Clean up Sphinx references --- docs/Validator.txt | 266 ++++++++++++++++++----------------- docs/conf.py | 7 +- docs/modules/schema.txt | 2 + docs/whatsnew-0-to-1.2.4.txt | 4 +- docs/whatsnew-1.2.5.txt | 2 +- src/formencode/compound.py | 12 +- src/formencode/schema.py | 2 +- 7 files changed, 152 insertions(+), 143 deletions(-) diff --git a/docs/Validator.txt b/docs/Validator.txt index 5166fdcd..7dc671c1 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -35,9 +35,9 @@ The basic metaphor for validation is **to_python** and -- the trusted application, your own Python objects. The "other" may be a web form, an external database, an XML-RPC request, or any data source that is not completely trusted or does not map directly to -Python's object model. :meth:`to_python` is the process of taking -external data and preparing it for internal use, :meth:`from_python` -generally reverses this process (:meth:`from_python` is usually the less +Python's object model. ``to_python()`` is the process of taking +external data and preparing it for internal use, ``from_python()`` +generally reverses this process (``from_python()`` is usually the less interesting of the pair, but provides some important features). The core of this validation process is two methods and an exception:: @@ -52,8 +52,8 @@ The core of this validation process is two methods and an exception:: ... Invalid: Please enter an integer value -``"ten"`` isn't a valid integer, so we get a :class:`formencode.Invalid` -exception. Typically we'd catch that exception, and use it for some +``"ten"`` isn't a valid integer, so we get a :class:`~formencode.api.Invalid` +exception. Typically, we'd catch that exception, and use it for some sort of feedback. Like: .. comment (fake input): @@ -97,7 +97,7 @@ We can also generalize this kind of function:: Enter your email: bob@nowhere.com 'bob@nowhere.com' -:class:`Invalid` exceptions generally give a good, user-readable error +``Invalid`` exceptions generally give a good, user-readable error message about the problem with the input. Using the exception gets more complicated when you use compound data structures (dictionaries and lists), which we'll talk about later__. @@ -148,24 +148,26 @@ they'll be specific to your application:: ... return value .. note:: - The class :class:`formencode.FancyValidator` is the superclass + The class `formencode.FancyValidator` is the superclass for most validators in FormEncode, and it provides a number of useful features that most validators can use -- for instance, you can pass ``strip=True`` into any of these validators, and they'll strip whitespace from the incoming value before any other validation. -This overrides the internal :meth:`_convert_to_python()` method: -:class:`formencode.FancyValidator` adds a number of extra features, and then -calls the internal :meth:`_convert_to_python` method, which is the method you'll -typically write. Contrary to the external method :meth:`to_python`, its -only concern is the conversion part, not the validation part. If further -validation is necessary, this can be done in two other internal methods, -either :meth:`_validate_python()` or :meth:`_validate_other()`. We will give -an example for that later. When a validator finds an error, it raises an -exception (:class:`formencode.Invalid`), with the error message and the -value and "state" objects. We'll talk about state_ later. Here's the -other custom validator, that checks passwords against words in the -standard Unix word file:: +This overrides the internal ``_convert_to_python()`` method: +:class:`formencode.api.FancyValidator` adds a number of extra features, +and then calls the internal ``_convert_to_python`` method, +which is the method you'll typically write. +Contrary to the external method ``to_python()``, its only concern is +the conversion part, not the validation part. +If further validation is necessary, +this can be done in two other internal methods, +either ``_validate_python()`` or ``_validate_other()``. +We will give an example for that later. When a validator finds an error, +it raises an exception (:class:`formencode.api.Invalid`), with the error +message and the value and "state" objects. We'll talk about state_ later. +Here's the other custom validator, that checks passwords against words +in the standard Unix word file:: >>> class SecurePassword(formencode.FancyValidator): ... words_filename = '/usr/share/dict/words' @@ -192,8 +194,8 @@ And here's a schema:: ... chained_validators = [validators.FieldsMatch( ... 'password', 'password_confirm')] -Like any other validator, a :class:`Registration` instance will have the -:meth:`to_python` and :meth:`from_python` methods. The input should be a +Like any other validator, a ``Registration`` instance will have the +``to_python`` and ``from_python`` methods. The input should be a dictionary (or a Paste MultiDict), with keys like ``"first_name"``, ``"password"``, etc. The validators you give as attributes will be applied to each of the values of the dictionary. *All* the values @@ -201,26 +203,27 @@ will be validated, so if there are multiple invalid fields you will get information about all of them. Most validators (anything that subclasses -:class:`formencode.FancyValidator`) will take a certain standard -set of constructor keyword arguments. See -:class:`formencode.api.FancyValidator` for more -- here we use -``not_empty=True``. +:class:`formencode.api.FancyValidator`) will take a certain standard +set of constructor keyword arguments. +See :class:`formencode.api.FancyValidator` for more +-- here we use ``not_empty=True``. -Another notable validator is :class:`formencode.compound.All` -- this +Another notable validator is :class:`~formencode.compound.All` -- this is a *compound validator* -- that is, it's a validator that takes -validators as input. Schemas are one example; in this case :class:`All` -takes a list of validators and applies each of them in turn. -:class:`formencode.compound.Any` is its compliment, that uses the +validators as input. Schemas are one example; in this case +:class:`~formencode.compound.All` takes a list of validators +and applies each of them in turn. +:class:`~formencode.compound.Any` is its compliment, that uses the first passing validator in its list. .. _pre_validators: .. _chained_validators: -:attr:`chained_validators` are validators that are run on the entire -dictionary after other validation is done (:attr:`pre_validators` are -applied before the schema validation). chained_validators will also +``chained_validators`` are validators that are run on the entire +dictionary after other validation is done (``pre_validators`` are +applied before the schema validation). ``chained_validators`` will also allow for multiple validators to fail and report to the error_dict -so, for example, if you have an email_confirm and a password_confirm +so, for example, if you have an ``email_confirm`` and a ``password_confirm`` fields and use FieldsMatch on both of them as follows: >>> chained_validators = [ @@ -255,16 +258,17 @@ function that you write. For example:: Invalid: state: You must enter a state -The :func:`validate_state` function (or any validation function) returns +The ``validate_state()`` function (or any validation function) returns any errors in the form (or it may raise Invalid directly). It can -also modify the :obj:`value_dict` dictionary directly. When it returns +also modify the ``value_dict`` dictionary directly. When it returns None this indicates that everything is valid. You can use this with a -:class:`Schema` by putting :class:`ValidateState` in :attr:`pre_validators` -(all validation will be done before the schema's validation, and if there's -an error the schema won't be run). Or you can put it in -:attr:`chained_validators` and it will be run *after* the schema. If the -schema fails (the values are invalid) then :class:`ValidateState` will not -be run, unless you set :attr:`validate_partial_form` to True (like +:class:`formencode.schema.Schema` by putting ``ValidateState`` in +``pre_validators`` (all validation will be done before the schema's +validation, and if there's an error the schema won't be run). +Or you can put it in ``chained_validators`` and it will be run +*after* the schema. If the schema fails (the values are invalid) +then ``ValidateState`` will not be run, unless you set +``validate_partial_form`` to True (like ``ValidateState = SimpleFormValidator(validate_state, validate_partial_form=True)``. If you validate a partial form you should be careful that you handle missing keys and other @@ -283,12 +287,12 @@ is for:: ... title = validators.ByteString(not_empty=True) >>> validator = formencode.ForEach(BookSchema()) -The :obj:`validator` we've created will take a list of dictionaries as +The ``validator`` we've created will take a list of dictionaries as input (like ``[{"id": "1", "title": "War & Peace"}, {"id": "2", -"title": "Brave New World"}, ...]``). It applies the :class:`BookSchema` -to each entry, and collects any errors and reraises them. Of course, -when you are validating input from an HTML form you won't get well -structured data like this (we'll talk about that later__). +"title": "Brave New World"}, ...]``). It applies the ``BookSchema`` +to each entry, and collects any errors and re-raises them. +Of course, when you are validating input from an HTML form you won't +get well-structured data like this (we'll talk about that later__). .. __: `HTTP/HTML Form Input`_ @@ -296,9 +300,8 @@ Writing Your Own Validator -------------------------- We gave a brief introduction to creating a validator earlier -(:class:`UniqueUsername` and :class:`SecurePassword`). We'll discuss -that a little more. Here's a more complete implementation of -:class:`SecurePassword`:: +(``UniqueUsername`` and ``SecurePassword``). We'll discuss that a little +more. Here's a more complete implementation of ``SecurePassword``:: >>> import re >>> class SecurePassword(validators.FancyValidator): @@ -332,27 +335,27 @@ that a little more. Here's a more complete implementation of ... value, state) With all validators, any arguments you pass to the constructor will be -used to set instance variables. So :class:`SecureValidator(min=5)` will +used to set instance variables. So ``SecureValidator(min=5)`` will be a minimum-five-character validator. This makes it easy to also subclass other validators, giving different default values. Unlike the previous implementation we use the already mentioned -:meth:`_validate_python` method, which is another internal method -:class:`FancyValidator` allows us to override. :meth:`_validate_python` -doesn't have any return value, it simply raises an exception if it -needs to. It validates the value *after* it has been converted -(by :meth:`_convert_to_python`). :meth:`_validate_other` validates before +``_validate_python`` method, which is another internal method +:class:`~formencode.api.FancyValidator` allows us to override. +``_validate_python`` doesn't have any return value, it simply raises an +exception if it needs to. It validates the value *after* it has been +converted (by ``_convert_to_python``). ``_validate_other`` validates before conversion, but that's usually not that useful. The external method -:meth:`to_python` cares about the extra features such as the -:attr:`if_empty` parameter, and uses the internal methods to do the -actual conversion and validation; first it calls :meth:`_validate_other`, -then :meth:`_convert_to_python` and at last :meth:`_validate_python`. +``to_python`` cares about the extra features such as the ``if_empty`` +parameter, and uses the internal methods to do the actual conversion +and validation; first it calls ``_validate_other``, then +``_convert_to_python`` and at last ``_validate_python``. The use of ``self.message(...)`` is meant to make the messages easy to -format for different environments, and replacable (with translations, +format for different environments, and replaceable (with translations, or simply with different text). Each message should have an identifier (``"min"`` and ``"non_letter"`` in this example). The -keyword arguments to :meth:`message` are used for message substitution. +keyword arguments to ``message`` are used for message substitution. See Messages_ for more. Other Validator Usage @@ -368,10 +371,9 @@ to set these. These are (effectively) equivalent:: ... regex = '^[a-zA-Z]+$' >>> plain = Plain() -You can actually use classes most places where you could use an -instance; :meth:`.to_python()` and :meth:`.from_python()` will create -instances as necessary, and many other methods are available on both -the instance and the class level. +You can actually use classes most places where you could use an instance; +``to_python()`` and ``from_python()`` will create instances as necessary, +and many other methods are available on both the instance and the class level. When dealing with nested validators this class syntax is often easier to work with, and better displays the structure. @@ -379,41 +381,39 @@ to work with, and better displays the structure. .. _FancyValidator: There are several options that most validators support (including your -own validators, if you subclass from :class:`formencode.FancyValidator`): - -:attr:`if_empty`: - If set, then this value will be returned if the input evaluates - to false (empty list, empty string, None, etc), but not the 0 or - False objects. This only applies to ``.to_python()``. - -:attr:`not_empty`: - If true, then if an empty value is given raise an error. - (Both with ``.to_python()`` and also ``.from_python()`` - if ``.validate_python`` is true). - -:attr:`strip`: - If true and the input is a string, strip it (occurs before empty - tests). - -:attr:`if_invalid`: - If set, then when this validator would raise Invalid during - ``.to_python()``, instead return this value. - -:attr:`if_invalid_python`: - If set, when the Python value (converted with - ``.from_python()``) is invalid, this value will be returned. - -:attr:`accept_python`: - If True (the default), then ``._validate_python()`` and - ``._validate_other()`` will not be called when - ``.from_python()`` is used. - -:attr:`if_missing`: - Typically when a field is missing the schema will raise an - error. In that case no validation is run -- so things like - ``if_invalid`` won't be triggered. This special attribute (if - set) will be used when the field is missing, and no error will - occur. (``None`` or ``()`` are common values) +own validators, if you subclass from :class:`formencode.api.FancyValidator`): + +``if_empty``: + If set, then this value will be returned if the input evaluates + to false (empty list, empty string, None, etc), but not the 0 or + False objects. This only applies to ``.to_python()``. + +``not_empty``: + If true, then if an empty value is given raise an error. + (Both with ``.to_python()`` and also ``.from_python()`` + if ``.validate_python`` is true). + +``strip``: + If true and the input is a string, strip it (occurs before empty tests). + +``if_invalid``: + If set, then when this validator would raise Invalid during + ``.to_python()``, instead return this value. + +``if_invalid_python``: + If set, when the Python value (converted with + ``.from_python()``) is invalid, this value will be returned. + +``accept_python``: + If True (the default), then ``._validate_python()`` and + ``._validate_other()`` will not be called when ``.from_python()`` is used. + +``if_missing``: + Typically when a field is missing the schema will raise an + error. In that case no validation is run -- so things like + ``if_invalid`` won't be triggered. This special attribute (if + set) will be used when the field is missing, and no error will + occur. (``None`` or ``()`` are common values) State ----- @@ -431,47 +431,49 @@ validator know the locale? State! Whatever else you need to pass in, just put it in the state object as an attribute, then look for that attribute in your validator. -Also, during compound validation (a :class:`formencode.schema.Schema` -or :class:`formencode.foreach.ForEach`) the state (if not None) will -have more instance variables added to it. During a :class:`Schema` -(dictionary) validation the instance variable ``key`` and -``full_dict`` will be added -- ``key`` is the current key (i.e., -validator name), and ``full_dict`` is the rest of the values being -validated. During a :class:`ForEeach` (list) validation, ``index`` and -``full_list`` will be set. +Also, during compound validation (a :class:`~formencode.schema.Schema` +or :class:`~formencode.foreach.ForEach`) the state (if not None) will +have more instance variables added to it. +During a :class:`~formencode.schema.Schema` (dictionary) validation +the instance variable ``key`` and ``full_dict`` will be added -- ``key`` +is the current key (i.e., validator name), and ``full_dict`` is the rest +of the values being validated. +During a :class:`~formencode.foreach.ForEach` (list) validation, +``index`` and ``full_list`` will be set. Invalid Exceptions ------------------ -Besides the string error message, :class:`formencode.Invalid` +Besides the string error message, :class:`~formencode.api.Invalid` exceptions have a few other instance variables: -:attr:`value`: +``value``: The input to the validator that failed. -:attr:`state`: +``state``: The associated state_. -:attr:`msg`: +``msg``: The error message (``str(exc)`` returns this) -:attr:`error_list`: - If the exception happened in a ``ForEach`` (list) validator, then - this will contain a list of ``Invalid`` exceptions. Each item - from the list will have an entry, either None for no error, or an - exception. +``error_list``: + If the exception happened in a :class:`~formencode.foreach.ForEach` + (list) validator, then this will contain a list of + :class:`~formencode.api.Invalid` exceptions. Each item from the list + will have an entry, either None for no error, or an exception. -:attr:`error_dict`: - If the exception happened in a :class:`Schema` (dictionary) validator, - then this will contain :class:`Invalid` exceptions for each failing - field. Passing fields not be included in this dictionary. +``error_dict``: + If the exception happened in a :class:`~formencode.schema.Schema` + (dictionary) validator, then this will contain + :class:`~formencode.api.Invalid` exceptions for each failing field. + Passing fields not be included in this dictionary. -:meth:`.unpack_errors()`: +``.unpack_errors()``: This method returns a set of lists and dictionaries containing - strings, for each error. It's an unpacking of :attr:`error_list`, - :attr:`error_dict` and :attr:`msg`. If you get an Invalid exception - from a :class:`Schema`, you probably want to call this method on the - exception object. + strings, for each error. It's an unpacking of ``error_list``, + ``error_dict`` and ``msg``. If you get an Invalid exception + from a :class:`~formencode.schema.Schema`, you probably want to call + this method on the exception object. .. _Messages: @@ -492,7 +494,7 @@ for each substitution. This way you can reorder or even ignore placeholders in your new message. When you are creating a validator, for maximum flexibility you should -use the :meth:`message` method, like:: +use the ``message`` method, like:: messages = { 'key': 'my message (with a %(substitution)s)', @@ -515,13 +517,13 @@ the found gettext function. To serve a standard translation mechanism and to enable custom translations it looks in the following order to find a gettext (``_``) function: -1. method of the :obj:`state` object +1. method of the ``state`` object -2. function :func:`__builtin__._`. This function is only used when:: +2. function ``builtin._``. This function is only used when:: - Validator.use_builtin_gettext == True #True is default + Validator.use_builtin_gettext == True # True is default -3. formencode builtin :func:`_stdtrans` function +3. formencode builtin ``_stdtrans`` function for standalone use of FormEncode. The language to use is determined out of the locale system (see gettext documentation). Optionally you @@ -534,7 +536,7 @@ find a gettext (``_``) function: messages in the directory ``localedir/language/LC_MESSAGES/FormEncode.mo`` -4. Custom gettext function and addtional parameters +4. Custom gettext function and additional parameters If you use a custom gettext function and you want FormEncode to call your function with additional parameters you can set the diff --git a/docs/conf.py b/docs/conf.py index 636bf3c2..ac4a06dd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,6 +9,11 @@ # All configuration values have a default; values that are commented out # serve to show the default. +# import os +# import sys +# sys.path.insert(0, os.path.join(os.path.dirname( +# os.path.dirname(os.path.abspath(__file__))), 'src')) + # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -40,7 +45,7 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/docs/modules/schema.txt b/docs/modules/schema.txt index f877abcb..c78d9856 100644 --- a/docs/modules/schema.txt +++ b/docs/modules/schema.txt @@ -8,3 +8,5 @@ Module Contents .. autoclass:: Schema +.. autoclass:: SimpleFormValidator + diff --git a/docs/whatsnew-0-to-1.2.4.txt b/docs/whatsnew-0-to-1.2.4.txt index b5e5ca47..94a4d6d9 100644 --- a/docs/whatsnew-0-to-1.2.4.txt +++ b/docs/whatsnew-0-to-1.2.4.txt @@ -60,7 +60,7 @@ What's New In FormEncode 0 to 1.2.4 * Added :class:`formencode.validators.IPAddress`, validating IP addresses, from Leandro Lucarella. -* Added :meth:`formencode.api.Invalid.__unicode__` +* Added method ``Invalid.__unicode__`` * In :mod:`formencode.htmlfill` use a default encoding of utf8 when handling mixed ``str``/``unicode`` content. Also do not modify @@ -125,7 +125,7 @@ What's New In FormEncode 0 to 1.2.4 ```` -- this will cause the error to be swallowed, not shown to the user. -* Added :class:`formencode.validators.XRI` for validation i-names, +* Added ``formencode.validators.XRI`` for validation i-names, i-numbers, URLs, etc (as used in OpenID). * Look in ``/usr/share/locale`` for locale files, in addition to the diff --git a/docs/whatsnew-1.2.5.txt b/docs/whatsnew-1.2.5.txt index c4ea4d30..9b4a8104 100644 --- a/docs/whatsnew-1.2.5.txt +++ b/docs/whatsnew-1.2.5.txt @@ -33,5 +33,5 @@ Backwards Incompatibilities Documentation Enhancements -------------------------- -- Superceded news with whatsnew documents that will be archived for each +- Superseded news with whatsnew documents that will be archived for each version. Archived all news prior to 1.2.5 in :doc:`/whatsnew-0-to-1.2.4`. diff --git a/src/formencode/compound.py b/src/formencode/compound.py index 4b3be890..5caf950d 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -130,12 +130,12 @@ def accept_iterator(self): class All(CompoundValidator): - """Check if all of the specified validators are valid. + """Check that specified validators are valid. - This class is like an 'and' operator for validators. All - validators must work, and the results are passed in turn through - all validators for conversion in the order of evaluation. All - is the same as `Pipe` but operates in the reverse order. + This class is like an 'and' operator for validators. + All validators must work, and the results are passed in turn through + all validators for conversion in the order of evaluation. + ``All`` is the same as ``Pipe`` but operates in the reverse order. The order of evaluation differs depending on if you are validating to Python or from Python as follows: @@ -144,7 +144,7 @@ class All(CompoundValidator): The validators are evaluated left to right when validating from Python. - `Pipe` is more intuitive when predominantly validating to Python. + ``Pipe`` is more intuitive when predominantly validating to Python. Examples:: diff --git a/src/formencode/schema.py b/src/formencode/schema.py index a4cfa089..7a14494d 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -4,7 +4,7 @@ from . import declarative from .exc import FERuntimeWarning -__all__ = ['Schema'] +__all__ = ['Schema', 'SimpleFormValidator'] class Schema(FancyValidator): From 9d4356f83a00a92baf6df5f971352c14cb4dd402 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 21:03:34 +0100 Subject: [PATCH 23/43] Simplify doc output test --- src/formencode/validators.py | 6 +++--- tests/non_empty.txt | 2 -- tests/test_doctests.py | 20 -------------------- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index a38ee503..f2011b47 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1168,11 +1168,11 @@ class Set(FancyValidator): ['this', 'that'] >>> s = Set(use_set=True) >>> s.to_python(None) - set([]) + set() >>> s.to_python('this') - set(['this']) + {'this'} >>> s.to_python(('this',)) - set(['this']) + {'this'} """ use_set = False diff --git a/tests/non_empty.txt b/tests/non_empty.txt index 8db8ec1d..bbed33e6 100644 --- a/tests/non_empty.txt +++ b/tests/non_empty.txt @@ -1,6 +1,4 @@ michele@ionic:~/Progetti/TurboGears/svn/thirdparty/formencode/formencode$ python -Python 2.4.3 (#2, Mar 30 2006, 14:45:01) -[GCC 4.0.3 (Ubuntu 4.0.3-1ubuntu3)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from formencode import validators >>> int = validators.Int() diff --git a/tests/test_doctests.py b/tests/test_doctests.py index 5361cace..b02d22db 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -28,26 +28,6 @@ base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -OutputChecker = doctest.OutputChecker - - -class OutputChecker3(OutputChecker): - def check_output(self, want, got, optionflags): - if want.startswith("u'"): - want = want[1:] - elif want.startswith("set(["): - want = ( - want[3:] - .replace("([", "{") - .replace("])", "}") - .replace("{}", "set()") - ) - return OutputChecker.check_output(self, want, got, optionflags) - - -doctest.OutputChecker = OutputChecker3 - - def doctest_file(document, verbose, raise_error): failure_count, test_count = doctest.testfile( document, From 50c866289169ea005bad3753a53725e5d616135f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 21:40:06 +0100 Subject: [PATCH 24/43] Allow Contexts to be used via context manager --- src/formencode/context.py | 12 +++++++++--- tests/test_context.py | 13 +++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/formencode/context.py b/src/formencode/context.py index 1f3ac8ea..f1c0b68b 100644 --- a/src/formencode/context.py +++ b/src/formencode/context.py @@ -44,9 +44,9 @@ def do_stuff(): variables to default values. This will not affect the stack of scopes, but will only add defaults. -this syntax would certainly be useful:: +A context can be also used like this:: - with context(page='view'): + with context.set(page='view'): do stuff... And ``page`` will be set to ``'view'`` only inside that ``with`` block. @@ -93,7 +93,7 @@ def __getattr__(self, attr): def __setattr__(self, attr, value): raise AttributeError( - "You can only write attribute on context object with the .set() method") + "You can only set attributes on context objects with the .set() method") def set(self, **kw): state_id = next(_restore_ids) @@ -158,6 +158,12 @@ def __init__(self, context, state_id): self.context = context self.restored = False + def __enter__(self): + return self.context + + def __exit__(self, _exc_type, _exc_value, _exc_tb): + self.restore() + def restore(self): if self.restored: # @@: Should this really be allowed? diff --git a/tests/test_context.py b/tests/test_context.py index 6e203f4d..34da8fa5 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -53,3 +53,16 @@ def test_default(): assert con.a == 2 res.restore() assert con.a == 4 + + +def test_context_manager(): + with c1.set(foo=1): + assert_is(c1, 'foo', 1) + assert_is(c1, 'foo', None) + with c1.set(foo=2), c2.set(foo='test'): + assert_is(c1, 'foo', 2) + assert_is(c2, 'foo', 'test') + change_state(c1, assert_is, c1, 'foo', 3, foo=3) + assert_is(c1, 'foo', 2) + assert_is(c1, 'foo', None) + assert not hasattr(c2, 'foo') From 7c3f1751c16a47ff934ec336674871b4272421ab Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 22:00:28 +0100 Subject: [PATCH 25/43] Fix deprecated setup.cfg setting --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ec6bfe79..74dc8a23 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,7 @@ maintainer = Chris Lambacher maintainer_email = chris@kateandchris.net url = "http://formencode.org" license = MIT -license_file = LICENSE.txt +license_files = LICENSE.txt classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers From 4f4ce89bd1264fcdf936c520a416544c849d2e66 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 22:00:49 +0100 Subject: [PATCH 26/43] Add changelog --- docs/whatsnew-2.0.txt | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 7b5adcc8..a4ff9cae 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -1,14 +1,22 @@ What's New In FormEncode 2.0 ============================ -This article explains the latest changes in `FormEncode` version 2.0.0 -compared to its predecessor, `FormEncode` 1.3.0 +This article explains the latest changes in `FormEncode` version 2.0 +compared to its predecessor, `FormEncode` version 1.3. +2.1.0 +----- + + - Add support for Python 3.7 to 3.22, end support for older Python versions + - Context.set() now works as a context manager + - Fix binary of swedish translation + - Some internal code cleanup and modernization 2.0.1 ----- - - Add support for 3.10 - - use Pytest instead of Nose and Github Actions instead of Travis for tests + + - Add support for Python 3.10 + - use Pytest instead of Nose and GitHub Actions instead of Travis for tests - Documentation updates - Note this will be the last version to support Python 2.7. The next version will be 2.1 to signal this change. If you want to keep support for Python @@ -17,12 +25,12 @@ compared to its predecessor, `FormEncode` 1.3.0 2.0.0 ----- - - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. - If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. - You might also try `FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. + - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. + - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. + If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. + You might also try FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. - This will be the last major version to support Python 2.7 - - Add strict flag to USPostalCode to raise error on postal codes that has too + - Add strict flag to ``USPostalCode`` to raise error on postal codes that has too many digits instead of just truncating - Various Python 3 fixes - Serbian latin translation @@ -30,6 +38,6 @@ compared to its predecessor, `FormEncode` 1.3.0 - Dutch, UK, Greek and South Korean postal code format fixes - Add postal code formats for Switzerland, Cyprus, Faroe Islands, San Marino, Ukraine and Vatican City. - Add ISODateTimeConverter validator - - Add ability to target htmlfill to particular form or ignore a form + - Add ability to target ``htmlfill`` to particular form or ignore a form - Fix format errors in some translations - - The version of the library can be checked using formencode.__version__ + - The version of the library can be checked using ``formencode.__version__`` From 8090410afc645933c8d1f855fa88223c991893d5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 22:11:02 +0100 Subject: [PATCH 27/43] Cleanup manifest and license files --- LICENSE.txt | 6 ------ MANIFEST.in | 1 - 2 files changed, 7 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index a611276f..20bd89b8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,5 @@ Copyright (c) 2015 Ian Bicking and FormEncode Contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -9,13 +7,9 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/MANIFEST.in b/MANIFEST.in index 44df65a8..8991e94a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,2 @@ exclude .gitignore exclude .gitattributes -exclude .travis.yml From a78247da3e9913769451700555084fa04854df5e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:08:43 +0100 Subject: [PATCH 28/43] Fix build configuration --- MANIFEST.in | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- setup.cfg | 14 ++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 8991e94a..81153722 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,25 @@ exclude .gitignore exclude .gitattributes + +include MANIFEST.in + +include LICENSE.txt +include README.rst + +include tox.ini + +graft src/formencode +graft src/formencode/javascript +include src/formencode/i18n/*.pot +recursive-include src/formencode/i18n *.po *.mo + +graft examples +recursive-include examples *.py + +graft tests +graft tests/htmlfill_data +recursive-include docs *.rst conf.py Makefile make.bat +prune docs/_build + +global-exclude */__pycache__/* +global-exclude *.py[cod] diff --git a/pyproject.toml b/pyproject.toml index c19dbc32..535641bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel", "setuptools_scm>=6.2"] +requires = ["setuptools", "wheel", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index 74dc8a23..16e5213e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,14 +28,17 @@ classifiers = Topic :: Software Development :: Libraries :: Python Modules [options] -packages = find: package_dir = - =src + = src +packages = + formencode python_requires = >=3.7 -include_package_data = True +include_package_data = False -[options.packages.find] -where = src +[options.package_data] +formencode = + javascript/*.js + i18n/**/*.mo [options.extras_require] testing = @@ -73,4 +76,3 @@ max_line_length=88 [flake8] max-line-length = 88 extend-ignore = E128,E402,E501,E731 - From 1681aa2651b5965aaa42dc72dd352a8598b3ebf3 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:15:51 +0100 Subject: [PATCH 29/43] Allow building with Python 3.7 again --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 535641bb..8a0985f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel", "setuptools_scm>=8"] +requires = ["setuptools", "wheel", "setuptools_scm>=7"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] From be73f002dfd33f4e2953be6cfca8d2b5e23ab941 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:22:41 +0100 Subject: [PATCH 30/43] Fix Read The Docs --- .readthedocs.yaml | 22 ++++++++++++++++++++++ MANIFEST.in | 1 + docs/requirements.txt | 1 + 3 files changed, 24 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..9712e405 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in index 81153722..549bdeb8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ exclude .gitignore exclude .gitattributes +exclude .readthedocs.yaml include MANIFEST.in diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..9cd8b2f5 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx>=7,<8 From 7d233ff7a3ff16c772d1a2f74accb0dff19063de Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:27:56 +0100 Subject: [PATCH 31/43] Install package in docs requirements --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 9cd8b2f5..fe64bbd4 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ sphinx>=7,<8 +-e .. From ec5ad13e581e6828fae27dc6962c8b3f93abcb1b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:34:13 +0100 Subject: [PATCH 32/43] Fix docs requirements --- .readthedocs.yaml | 2 +- docs/requirements.txt | 2 -- requirements-docs.txt | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 docs/requirements.txt create mode 100644 requirements-docs.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9712e405..3dd84c58 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -19,4 +19,4 @@ sphinx: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - - requirements: docs/requirements.txt + - requirements: requirements-docs.txt diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index fe64bbd4..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -sphinx>=7,<8 --e .. diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 00000000..ac1ea171 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,2 @@ +sphinx >=7, <8 +-e . From 450ebe9025c65f88f826738811deb203ec32b104 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:40:46 +0100 Subject: [PATCH 33/43] Fix changelog --- docs/whatsnew-2.0.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index a4ff9cae..370f805a 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -7,7 +7,7 @@ compared to its predecessor, `FormEncode` version 1.3. 2.1.0 ----- - - Add support for Python 3.7 to 3.22, end support for older Python versions + - Add support for Python 3.7 to 3.12, end support for older Python versions - Context.set() now works as a context manager - Fix binary of swedish translation - Some internal code cleanup and modernization @@ -18,18 +18,17 @@ compared to its predecessor, `FormEncode` version 1.3. - Add support for Python 3.10 - use Pytest instead of Nose and GitHub Actions instead of Travis for tests - Documentation updates - - Note this will be the last version to support Python 2.7. The next version - will be 2.1 to signal this change. If you want to keep support for Python - 2.7 update your dependencies spec to be below 2.1 + - Note this will be the last version to support Python 2. + The next version will be 2.1.0 to signal this change. + For compatibility with older Python versions, please use versions < 2.1. 2.0.0 ----- - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. - If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. - You might also try FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. - - This will be the last major version to support Python 2.7 + For compatibility with older Python versions, please use versions < 1.3. + - This will be the last major version to support Python 2. - Add strict flag to ``USPostalCode`` to raise error on postal codes that has too many digits instead of just truncating - Various Python 3 fixes From aa823839a1036eb5c25b0be42486b2ffe8f16645 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:52:47 +0100 Subject: [PATCH 34/43] Add badges and fix homepage --- README.rst | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9b556630..2ebd9bdc 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,14 @@ FormEncode ========== +.. |PyPI| image:: https://img.shields.io/pypi/v/formencode + :target: https://pypi.org/project/formencode + :alt: PyPI + +.. |Python| image:: https://img.shields.io/pypi/pyversions/formencode + :target: https://pypi.org/project/formencode + :alt: PyPI - Python Version + .. image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg :target: https://github.com/formencode/formencode/actions :alt: Test Status diff --git a/setup.cfg b/setup.cfg index 16e5213e..a2fd5767 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ author = Ian Bicking author_email = ianb@colorstudy.com maintainer = Chris Lambacher maintainer_email = chris@kateandchris.net -url = "http://formencode.org" +url = https://www.formencode.org/ license = MIT license_files = LICENSE.txt classifiers = From 64f6726a630f37aad65e6ecaa4145d1c88a711a2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:16:24 +0100 Subject: [PATCH 35/43] Use https for the documentation --- README.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 2ebd9bdc..958f0712 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,14 @@ FormEncode ========== .. |PyPI| image:: https://img.shields.io/pypi/v/formencode - :target: https://pypi.org/project/formencode - :alt: PyPI + :target: https://pypi.org/project/formencode + :alt: PyPI .. |Python| image:: https://img.shields.io/pypi/pyversions/formencode - :target: https://pypi.org/project/formencode - :alt: PyPI - Python Version + :target: https://pypi.org/project/formencode + :alt: PyPI - Python Version -.. image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg +.. |Tests| image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg :target: https://github.com/formencode/formencode/actions :alt: Test Status @@ -17,16 +17,16 @@ FormEncode Introduction ------------ -FormEncode is a validation and form generation package. The -validation can be used separately from the form generation. The -validation works on compound data structures, with all parts being -nestable. It is separate from HTTP or any other input mechanism. +FormEncode is a validation and form generation package. +The validation can be used separately from the form generation. +The validation works on compound data structures, with all parts being nestable. +It is separate from HTTP or any other input mechanism. Documentation ------------- -The latest documentation is available at http://www.formencode.org/ +The latest documentation is available at https://www.formencode.org/ Testing @@ -39,4 +39,4 @@ Use `tox` to run the test suite for all supported Python versions. Changes ------- -See the `What's new section of the documentation `_. +See the `What's new section of the documentation `_. From 43368d5eb1bed385040577beee01a072da48685f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:19:05 +0100 Subject: [PATCH 36/43] Fix badges --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 958f0712..3b9a3ee2 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,8 @@ FormEncode ========== +|PyPI| |Python| |Tests| + .. |PyPI| image:: https://img.shields.io/pypi/v/formencode :target: https://pypi.org/project/formencode :alt: PyPI From ec2ea1b0617601885ddaee0ec499820166bfed58 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:30:49 +0100 Subject: [PATCH 37/43] Make README a bit nicer --- README.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 3b9a3ee2..4d9f6870 100644 --- a/README.rst +++ b/README.rst @@ -28,17 +28,19 @@ It is separate from HTTP or any other input mechanism. Documentation ------------- -The latest documentation is available at https://www.formencode.org/ +The latest documentation is available at +`www.formencode.org `. Testing ------- -Use `pytest formencode` to run the test suite. -Use `tox` to run the test suite for all supported Python versions. +Use ``pytest formencode`` to run the test suite. +Use ``tox`` to run the test suite for all supported Python versions. Changes ------- -See the `What's new section of the documentation `_. +See the `What's new `_ +section of the documentation. From 28c90007e10bc520bcdc664abe806e59c7672d9e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:32:20 +0100 Subject: [PATCH 38/43] Fix syntax in README file --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4d9f6870..76d97684 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ Documentation ------------- The latest documentation is available at -`www.formencode.org `. +`www.formencode.org `_. Testing @@ -42,5 +42,5 @@ Use ``tox`` to run the test suite for all supported Python versions. Changes ------- -See the `What's new `_ +See the `What's New `_ section of the documentation. From 43edbb5fd007ff352a6b75719aee466ae916dbab Mon Sep 17 00:00:00 2001 From: Oleg Broytman Date: Fri, 10 Nov 2023 18:46:03 +0300 Subject: [PATCH 39/43] Prepare for Python 3.13 which removes the cgi module (#176) --- src/formencode/validators.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index f2011b47..6f272b78 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -5,7 +5,10 @@ Validator/Converters for use with FormEncode. """ -import cgi +try: + import cgi +except ImportError: # Python >= 3.13 + cgi = None import re import warnings from encodings import idna @@ -1772,7 +1775,7 @@ class FieldStorageUploadConverter(FancyValidator): no upload was given). """ def _convert_to_python(self, value, state=None): - if isinstance(value, cgi.FieldStorage): + if cgi and isinstance(value, cgi.FieldStorage): if getattr(value, 'filename', None): return value raise Invalid('invalid', value, state) @@ -1780,7 +1783,7 @@ def _convert_to_python(self, value, state=None): return value def is_empty(self, value): - if isinstance(value, cgi.FieldStorage): + if cgi and isinstance(value, cgi.FieldStorage): return not bool(getattr(value, 'filename', None)) return FancyValidator.is_empty(self, value) @@ -1825,7 +1828,7 @@ def _convert_to_python(self, value, state): upload = value.get(self.upload_key) static = value.get(self.static_key, '').strip() filename = content = None - if isinstance(upload, cgi.FieldStorage): + if cgi and isinstance(upload, cgi.FieldStorage): filename = upload.filename content = upload.value elif isinstance(upload, str) and upload: From 298bbb4d600b5cedbfe699b378332ee3b04036f2 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Thu, 30 Jan 2025 08:38:08 -0500 Subject: [PATCH 40/43] Fix test automation This fixes tests on Github that are failing for several reasons: - Python 3.7 is not supported on the latest Ubuntu and MacOS images - The URL doctest is failing on a redirect from http to https --- .github/workflows/run-tests.yml | 13 ++++++++++--- setup.cfg | 1 + src/formencode/validators.py | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 0aaad708..602ac332 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,14 +13,21 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] os: [ubuntu-latest, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. - - python-version: 'pypy-3.9' + - python-version: 'pypy3.10' os: ubuntu-latest - - python-version: 'pypy-3.10' + - python-version: '3.7' + os: ubuntu-22.04 + - python-version: '3.7' + os: macos-13 + exclude: + - python-version: '3.7' + os: macOS-latest + - python-version: '3.7' os: ubuntu-latest steps: diff --git a/setup.cfg b/setup.cfg index a2fd5767..7ebd9621 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ classifiers = Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 6f272b78..4666f70a 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1397,7 +1397,7 @@ class URL(FancyValidator): Traceback (most recent call last): ... Invalid: You must start your URL with http://, https://, etc - >>> u.to_python('http://www.formencode.org/does/not/exist/page.html') + >>> u.to_python('https://httpbin.org/status/404') Traceback (most recent call last): ... Invalid: The server responded that the page could not be found @@ -1660,7 +1660,7 @@ def _validate_python(self, value, state=None): if value.startswith('xri://'): value = value[6:] - if not value[0] in ('@', '=') and not ( + if value[0] not in ('@', '=') and not ( self.xri_type == 'i-number' and value[0] == '!'): raise Invalid(self.message('noType', state), value, state) From a98fcd88469abbfa997847bacc31d022dd1b6bf9 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Thu, 30 Jan 2025 09:20:20 -0500 Subject: [PATCH 41/43] Add automation for releases when a tag is created --- .github/workflows/publish-to-pypi.yml | 88 +++++++++++++++++++++++++++ Makefile | 6 -- 2 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/publish-to-pypi.yml diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml new file mode 100644 index 00000000..7cb567a4 --- /dev/null +++ b/.github/workflows/publish-to-pypi.yml @@ -0,0 +1,88 @@ +name: Publish Python distribution to PyPI + +on: push + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: python3 -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + publish-to-pypi: + name: Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/FormEncode + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + github-release: + name: >- + Sign the Python distribution with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + "$GITHUB_REF_NAME" + --repo "$GITHUB_REPOSITORY" + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + "$GITHUB_REF_NAME" dist/** + --repo "$GITHUB_REPOSITORY" diff --git a/Makefile b/Makefile index b27f4fd5..4a5d06db 100644 --- a/Makefile +++ b/Makefile @@ -13,12 +13,6 @@ flake8: coverage: pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode -publish: - pip install "twine>=4" build - python -m build - twine upload dist/* - rm -fr build dist .egg src/FormEncode.egg-info - .PHONY: docs docs: From 8375977e58f1ceea7542334f2777c01901525ed9 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Fri, 31 Jan 2025 09:45:07 -0500 Subject: [PATCH 42/43] Don't allow FieldStorageUploadConverter without cgi module In Python 3.13 and later the cgi module has been removed and this meant that importing the `cgi` module started making it so `formencode.validators` could not be imported. This was resolved in #176 by detecting the import error, setting `cgi` to `None`, and then skipping behaviour that used `cgi.FieldStorage`. Using `FieldStorageUploadConverter` without `cgi.FieldStorage` does not make sense. This change adds a check to `FieldStorageUploadConverter.__init_` to prevent instantiating it like we do for DNS validators so that using it doesn't silently do nothing. Probably if you are using FieldStorageUploadConverter then you'll have `legacy-cgi` installed because you are either already using it directly, or you are getting it transitively through something like WebOb. --- src/formencode/validators.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 4666f70a..e17777f1 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1773,7 +1773,19 @@ class FieldStorageUploadConverter(FancyValidator): This doesn't do any conversion, but it can detect empty upload fields (which appear like normal fields, but have no filename when no upload was given). + + Requires the legacy-cgi package on Python 3.13 and later. """ + + def __init__(self, *args, **kw): + if cgi is None: + warnings.warn( + "legacy-cgi is not" + " installed on your system (or the cgi package cannot be" + " found). I cannot convert FieldStorage") + raise ImportError("no module named cgi") + super().__init__(*args, **kw) + def _convert_to_python(self, value, state=None): if cgi and isinstance(value, cgi.FieldStorage): if getattr(value, 'filename', None): @@ -1819,6 +1831,9 @@ class MyScheme(Scheme): Note that big file uploads mean big hidden fields, and lots of bytes passed back and forth in the case of an error. + + Note: requires the legacy-cgi package on Python 3.13 and later to be able to handle + ``cgi.FieldStorage`` values. """ upload_key = 'upload' From c2969467b209d93f09e32ebaf75409da72d9ecff Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Fri, 31 Jan 2025 10:00:42 -0500 Subject: [PATCH 43/43] Update docs for 2.1.1 release --- docs/whatsnew-2.0.txt | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 370f805a..9a54e515 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -1,9 +1,24 @@ -What's New In FormEncode 2.0 +What's New In FormEncode 2.x ============================ -This article explains the latest changes in `FormEncode` version 2.0 +This article explains the latest changes in `FormEncode` version 2.x compared to its predecessor, `FormEncode` version 1.3. +2.1.1 +----- + + - Add support for 3.13 + - Don't require `legacy-cgi` to be installed on 3.13 and later + (`#176 `_) + - Don't permit `FieldStorageUploadConverter` to be instantiated without + having `legacy-cgi` installed since it does not make sense + - Releases are now automated through GitHub Actions + (`#184 `_) + + +Note: This is the last version that will support Python 3.7 and 3.8 as +those are now out of support. + 2.1.0 ----- @@ -25,8 +40,8 @@ compared to its predecessor, `FormEncode` version 1.3. 2.0.0 ----- - - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. + - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. + - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. For compatibility with older Python versions, please use versions < 1.3. - This will be the last major version to support Python 2. - Add strict flag to ``USPostalCode`` to raise error on postal codes that has too