diff --git a/last_commit.txt b/last_commit.txt index b1f54a8e5d..e69de29bb2 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,504 +0,0 @@ -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T11:35:55+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/5b5d67e22ae4449a5edca8ae18b7d9d5daf664e4 - -Configuring with plone/meta - -Files changed: -A .editorconfig -A .flake8 -A .github/workflows/meta.yml -A .meta.toml -A .pre-commit-config.yaml -A dependabot.yml -A news/+meta.internal -A tox.ini -M .gitignore -M pyproject.toml - -b'diff --git a/.editorconfig b/.editorconfig\nnew file mode 100644\nindex 0000000..5b3c112\n--- /dev/null\n+++ b/.editorconfig\n@@ -0,0 +1,56 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+#\n+# EditorConfig Configuration file, for more details see:\n+# http://EditorConfig.org\n+# EditorConfig is a convention description, that could be interpreted\n+# by multiple editors to enforce common coding conventions for specific\n+# file types\n+\n+# top-most EditorConfig file:\n+# Will ignore other EditorConfig files in Home directory or upper tree level.\n+root = true\n+\n+\n+[*]\n+# Default settings for all files.\n+# Unix-style newlines with a newline ending every file\n+end_of_line = lf\n+insert_final_newline = true\n+trim_trailing_whitespace = true\n+# Set default charset\n+charset = utf-8\n+# Indent style default\n+indent_style = space\n+# Max Line Length - a hard line wrap, should be disabled\n+max_line_length = off\n+\n+[*.{py,cfg,ini}]\n+# 4 space indentation\n+indent_size = 4\n+\n+[*.{yml,zpt,pt,dtml,zcml,html,xml}]\n+# 2 space indentation\n+indent_size = 2\n+\n+[*.{json,jsonl,js,jsx,ts,tsx,css,less,scss}]\n+# Frontend development\n+# 2 space indentation\n+indent_size = 2\n+max_line_length = 80\n+\n+[{Makefile,.gitmodules}]\n+# Tab indentation (no size specified, but view as 4 spaces)\n+indent_style = tab\n+indent_size = unset\n+tab_width = unset\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [editorconfig]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/.flake8 b/.flake8\nnew file mode 100644\nindex 0000000..38918f4\n--- /dev/null\n+++ b/.flake8\n@@ -0,0 +1,22 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[flake8]\n+doctests = 1\n+ignore =\n+ # black takes care of line length\n+ E501,\n+ # black takes care of where to break lines\n+ W503,\n+ # black takes care of spaces within slicing (list[:])\n+ E203,\n+ # black takes care of spaces after commas\n+ E231,\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [flake8]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/.github/workflows/meta.yml b/.github/workflows/meta.yml\nnew file mode 100644\nindex 0000000..c9848bc\n--- /dev/null\n+++ b/.github/workflows/meta.yml\n@@ -0,0 +1,75 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+name: Meta\n+on:\n+ push:\n+ branches:\n+ - master\n+ - main\n+ pull_request:\n+ branches:\n+ - master\n+ - main\n+ workflow_dispatch:\n+\n+##\n+# To set environment variables for all jobs, add in .meta.toml:\n+# [github]\n+# env = """\n+# debug: 1\n+# image-name: \'org/image\'\n+# image-tag: \'latest\'\n+# """\n+##\n+\n+jobs:\n+ qa:\n+ uses: plone/meta/.github/workflows/qa.yml@main\n+ test:\n+ uses: plone/meta/.github/workflows/test.yml@main\n+ coverage:\n+ uses: plone/meta/.github/workflows/coverage.yml@main\n+ dependencies:\n+ uses: plone/meta/.github/workflows/dependencies.yml@main\n+ release_ready:\n+ uses: plone/meta/.github/workflows/release_ready.yml@main\n+ circular:\n+ uses: plone/meta/.github/workflows/circular.yml@main\n+\n+##\n+# To modify the list of default jobs being created add in .meta.toml:\n+# [github]\n+# jobs = [\n+# "qa",\n+# "test",\n+# "coverage",\n+# "dependencies",\n+# "release_ready",\n+# "circular",\n+# ]\n+##\n+\n+##\n+# To request that some OS level dependencies get installed\n+# when running tests/coverage jobs, add in .meta.toml:\n+# [github]\n+# os_dependencies = "git libxml2 libxslt"\n+##\n+\n+##\n+# To test against a specific matrix of python versions\n+# when running tests jobs, add in .meta.toml:\n+# [github]\n+# py_versions = "[\'3.12\', \'3.11\']"\n+##\n+\n+\n+##\n+# Specify additional jobs in .meta.toml:\n+# [github]\n+# extra_lines = """\n+# another:\n+# uses: org/repo/.github/workflows/file.yml@main\n+# """\n+##\ndiff --git a/.gitignore b/.gitignore\nindex 1a4e837..486392f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -1,9 +1,56 @@\n-bin/\n-lib*\n-include/\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+# python related\n *.egg-info\n-.installed.cfg\n-bootstrap.py\n+*.pyc\n+*.pyo\n+\n+# translation related\n+*.mo\n+\n+# tools related\n+build/\n+.coverage\n+.*project\n+coverage.xml\n+dist/\n+docs/_build\n+__pycache__/\n+.tox\n+.vscode/\n+node_modules/\n+\n+# venv / buildout related\n+bin/\n develop-eggs/\n-*.py[co]\n-pip-selfcheck.json\n+eggs/\n+.eggs/\n+etc/\n+.installed.cfg\n+include/\n+lib/\n+lib64\n+.mr.developer.cfg\n+parts/\n+pyvenv.cfg\n+var/\n+local.cfg\n+\n+# mxdev\n+/instance/\n+/.make-sentinels/\n+/*-mxdev.txt\n+/reports/\n+/sources/\n+/venv/\n+.installed.txt\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [gitignore]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/.meta.toml b/.meta.toml\nnew file mode 100644\nindex 0000000..83eecdf\n--- /dev/null\n+++ b/.meta.toml\n@@ -0,0 +1,6 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[meta]\n+template = "default"\n+commit-id = "4b86f8f1"\ndiff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml\nnew file mode 100644\nindex 0000000..3479cc2\n--- /dev/null\n+++ b/.pre-commit-config.yaml\n@@ -0,0 +1,94 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+ci:\n+ autofix_prs: false\n+ autoupdate_schedule: monthly\n+\n+repos:\n+- repo: https://github.com/asottile/pyupgrade\n+ rev: v3.16.0\n+ hooks:\n+ - id: pyupgrade\n+ args: [--py38-plus]\n+- repo: https://github.com/pycqa/isort\n+ rev: 5.13.2\n+ hooks:\n+ - id: isort\n+- repo: https://github.com/psf/black\n+ rev: 24.4.2\n+ hooks:\n+ - id: black\n+- repo: https://github.com/collective/zpretty\n+ rev: 3.1.0\n+ hooks:\n+ - id: zpretty\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# zpretty_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+- repo: https://github.com/PyCQA/flake8\n+ rev: 7.1.0\n+ hooks:\n+ - id: flake8\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# flake8_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+- repo: https://github.com/codespell-project/codespell\n+ rev: v2.3.0\n+ hooks:\n+ - id: codespell\n+ additional_dependencies:\n+ - tomli\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# codespell_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+- repo: https://github.com/mgedmin/check-manifest\n+ rev: "0.49"\n+ hooks:\n+ - id: check-manifest\n+- repo: https://github.com/regebro/pyroma\n+ rev: "4.2"\n+ hooks:\n+ - id: pyroma\n+- repo: https://github.com/mgedmin/check-python-versions\n+ rev: "0.22.0"\n+ hooks:\n+ - id: check-python-versions\n+ args: [\'--only\', \'setup.py,pyproject.toml\']\n+- repo: https://github.com/collective/i18ndude\n+ rev: "6.2.0"\n+ hooks:\n+ - id: i18ndude\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# i18ndude_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/dependabot.yml b/dependabot.yml\nnew file mode 100644\nindex 0000000..bbd3ab0\n--- /dev/null\n+++ b/dependabot.yml\n@@ -0,0 +1,11 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+version: 2\n+updates:\n+\n+ - package-ecosystem: "github-actions"\n+ directory: "/"\n+ schedule:\n+ # Check for updates to GitHub Actions every week\n+ interval: "weekly"\ndiff --git a/news/+meta.internal b/news/+meta.internal\nnew file mode 100644\nindex 0000000..c08f539\n--- /dev/null\n+++ b/news/+meta.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone devs]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..653b7e2 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -1,6 +1,12 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[build-system]\n+requires = ["setuptools>=68.2"]\n+\n [tool.towncrier]\n-filename = "CHANGES.rst"\n directory = "news/"\n+filename = "CHANGES.rst"\n title_format = "{version} ({project_date})"\n underlines = ["-", ""]\n \n@@ -18,3 +24,143 @@ showcontent = true\n directory = "bugfix"\n name = "Bug fixes:"\n showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "internal"\n+name = "Internal:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "documentation"\n+name = "Documentation:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "tests"\n+name = "Tests"\n+showcontent = true\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# towncrier_extra_lines = """\n+# extra_configuration\n+# """\n+##\n+\n+[tool.isort]\n+profile = "plone"\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# isort_extra_lines = """\n+# extra_configuration\n+# """\n+##\n+\n+[tool.black]\n+target-version = ["py38"]\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# black_extra_lines = """\n+# extra_configuration\n+# """\n+##\n+\n+[tool.codespell]\n+ignore-words-list = "discreet,assertin,"\n+skip = "*.po,"\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# codespell_ignores = "foo,bar"\n+# codespell_skip = "*.po,*.map,package-lock.json"\n+##\n+\n+[tool.dependencychecker]\n+Zope = [\n+ # Zope own provided namespaces\n+ \'App\', \'OFS\', \'Products.Five\', \'Products.OFSP\', \'Products.PageTemplates\',\n+ \'Products.SiteAccess\', \'Shared\', \'Testing\', \'ZPublisher\', \'ZTUtils\',\n+ \'Zope2\', \'webdav\', \'zmi\',\n+ # ExtensionClass own provided namespaces\n+ \'ExtensionClass\', \'ComputedAttribute\', \'MethodObject\',\n+ # Zope dependencies\n+ \'AccessControl\', \'Acquisition\', \'AuthEncoding\', \'beautifulsoup4\', \'BTrees\',\n+ \'cffi\', \'Chameleon\', \'DateTime\', \'DocumentTemplate\',\n+ \'MultiMapping\', \'multipart\', \'PasteDeploy\', \'Persistence\', \'persistent\',\n+ \'pycparser\', \'python-gettext\', \'pytz\', \'RestrictedPython\', \'roman\',\n+ \'soupsieve\', \'transaction\', \'waitress\', \'WebOb\', \'WebTest\', \'WSGIProxy2\',\n+ \'z3c.pt\', \'zc.lockfile\', \'ZConfig\', \'zExceptions\', \'ZODB\', \'zodbpickle\',\n+ \'zope.annotation\', \'zope.browser\', \'zope.browsermenu\', \'zope.browserpage\',\n+ \'zope.browserresource\', \'zope.cachedescriptors\', \'zope.component\',\n+ \'zope.configuration\', \'zope.container\', \'zope.contentprovider\',\n+ \'zope.contenttype\', \'zope.datetime\', \'zope.deferredimport\',\n+ \'zope.deprecation\', \'zope.dottedname\', \'zope.event\', \'zope.exceptions\',\n+ \'zope.filerepresentation\', \'zope.globalrequest\', \'zope.hookable\',\n+ \'zope.i18n\', \'zope.i18nmessageid\', \'zope.interface\', \'zope.lifecycleevent\',\n+ \'zope.location\', \'zope.pagetemplate\', \'zope.processlifetime\', \'zope.proxy\',\n+ \'zope.ptresource\', \'zope.publisher\', \'zope.schema\', \'zope.security\',\n+ \'zope.sequencesort\', \'zope.site\', \'zope.size\', \'zope.structuredtext\',\n+ \'zope.tal\', \'zope.tales\', \'zope.testbrowser\', \'zope.testing\',\n+ \'zope.traversing\', \'zope.viewlet\'\n+]\n+\'Products.CMFCore\' = [\n+ \'docutils\', \'five.localsitemanager\', \'Missing\', \'Products.BTreeFolder2\',\n+ \'Products.GenericSetup\', \'Products.MailHost\', \'Products.PythonScripts\',\n+ \'Products.StandardCacheManagers\', \'Products.ZCatalog\', \'Record\',\n+ \'zope.sendmail\', \'Zope\'\n+]\n+\'plone.base\' = [\n+ \'plone.batching\', \'plone.registry\', \'plone.schema\',\'plone.z3cform\',\n+ \'Products.CMFCore\', \'Products.CMFDynamicViewFTI\',\n+]\n+python-dateutil = [\'dateutil\']\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# dependencies_ignores = "[\'zestreleaser.towncrier\']"\n+# dependencies_mappings = [\n+# "gitpython = [\'git\']",\n+# "pygithub = [\'github\']",\n+# ]\n+##\n+\n+[tool.check-manifest]\n+ignore = [\n+ ".editorconfig",\n+ ".flake8",\n+ ".meta.toml",\n+ ".pre-commit-config.yaml",\n+ "dependabot.yml",\n+ "mx.ini",\n+ "tox.ini",\n+\n+]\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# check_manifest_ignores = """\n+# "*.map.js",\n+# "*.pyc",\n+# """\n+# check_manifest_extra_lines = """\n+# ignore-bad-ideas = [\n+# "some/test/file/PKG-INFO",\n+# ]\n+# """\n+##\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/tox.ini b/tox.ini\nnew file mode 100644\nindex 0000000..d9467e8\n--- /dev/null\n+++ b/tox.ini\n@@ -0,0 +1,220 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[tox]\n+# We need 4.4.0 for constrain_package_deps.\n+min_version = 4.4.0\n+envlist =\n+ lint\n+ test\n+ dependencies\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# envlist_lines = """\n+# my_other_environment\n+# """\n+# config_lines = """\n+# my_extra_top_level_tox_configuration_lines\n+# """\n+##\n+\n+[testenv]\n+skip_install = true\n+allowlist_externals =\n+ echo\n+ false\n+# Make sure typos like `tox -e formaat` are caught instead of silently doing nothing.\n+# See https://github.com/tox-dev/tox/issues/2858.\n+commands =\n+ echo "Unrecognized environment name {envname}"\n+ false\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# testenv_options = """\n+# basepython = /usr/bin/python3.8\n+# """\n+##\n+\n+[testenv:init]\n+description = Prepare environment\n+skip_install = true\n+commands =\n+ echo "Initial setup complete"\n+\n+\n+[testenv:format]\n+description = automatically reformat code\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a pyupgrade\n+ pre-commit run -a isort\n+ pre-commit run -a black\n+ pre-commit run -a zpretty\n+\n+[testenv:lint]\n+description = run linters that will help improve the code style\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a\n+\n+[testenv:dependencies]\n+description = check if the package defines all its dependencies\n+skip_install = true\n+deps =\n+ build\n+ z3c.dependencychecker==2.14.3\n+commands =\n+ python -m build --sdist\n+ dependencychecker\n+\n+[testenv:dependencies-graph]\n+description = generate a graph out of the dependencies of the package\n+skip_install = false\n+allowlist_externals =\n+ sh\n+deps =\n+ pipdeptree==2.5.1\n+ graphviz # optional dependency of pipdeptree\n+commands =\n+ sh -c \'pipdeptree --exclude setuptools,wheel,pipdeptree,zope.interface,zope.component --graph-output svg > dependencies.svg\'\n+\n+[testenv:test]\n+description = run the distribution tests\n+use_develop = true\n+skip_install = false\n+constrain_package_deps = true\n+set_env =\n+ ROBOT_BROWSER=headlesschrome\n+\n+##\n+# Specify extra test environment variables in .meta.toml:\n+# [tox]\n+# test_environment_variables = """\n+# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/\n+# """\n+#\n+# Set constrain_package_deps .meta.toml:\n+# [tox]\n+# constrain_package_deps = "false"\n+##\n+deps =\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+##\n+# Specify additional deps in .meta.toml:\n+# [tox]\n+# test_deps_additional = "-esources/plonegovbr.portal_base[test]"\n+#\n+# Specify a custom constraints file in .meta.toml:\n+# [tox]\n+# constraints_file = "https://my-server.com/constraints.txt"\n+##\n+commands =\n+ zope-testrunner --all --test-path={toxinidir} -s Products.validation {posargs}\n+extras =\n+ test\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# test_extras = """\n+# tests\n+# widgets\n+# """\n+##\n+\n+[testenv:coverage]\n+description = get a test coverage report\n+use_develop = true\n+skip_install = false\n+constrain_package_deps = true\n+set_env =\n+ ROBOT_BROWSER=headlesschrome\n+\n+##\n+# Specify extra test environment variables in .meta.toml:\n+# [tox]\n+# test_environment_variables = """\n+# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/\n+# """\n+##\n+deps =\n+ coverage\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+commands =\n+ coverage run --branch --source Products.validation {envbindir}/zope-testrunner --quiet --all --test-path={toxinidir} -s Products.validation {posargs}\n+ coverage report -m --format markdown\n+ coverage xml\n+ coverage html\n+extras =\n+ test\n+\n+\n+[testenv:release-check]\n+description = ensure that the distribution is ready to release\n+skip_install = true\n+deps =\n+ twine\n+ build\n+ towncrier\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+commands =\n+ # fake version to not have to install the package\n+ # we build the change log as news entries might break\n+ # the README that is displayed on PyPI\n+ towncrier build --version=100.0.0 --yes\n+ python -m build --sdist\n+ twine check dist/*\n+\n+[testenv:circular]\n+description = ensure there are no cyclic dependencies\n+use_develop = true\n+skip_install = false\n+# Here we must always constrain the package deps to what is already installed,\n+# otherwise we simply get the latest from PyPI, which may not work.\n+constrain_package_deps = true\n+set_env =\n+\n+##\n+# Specify extra test environment variables in .meta.toml:\n+# [tox]\n+# test_environment_variables = """\n+# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/\n+# """\n+##\n+allowlist_externals =\n+ sh\n+deps =\n+ pipdeptree\n+ pipforester\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+commands =\n+ # Generate the full dependency tree\n+ sh -c \'pipdeptree -j > forest.json\'\n+ # Generate a DOT graph with the circular dependencies, if any\n+ pipforester -i forest.json -o forest.dot --cycles\n+ # Report if there are any circular dependencies, i.e. error if there are any\n+ pipforester -i forest.json --check-cycles -o /dev/null\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T11:37:34+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/72c1d5a0911d425699030e498411c7033197a149 - -Remove no longer needed setup.cfg. - -Files changed: -D setup.cfg - -b'diff --git a/setup.cfg b/setup.cfg\ndeleted file mode 100644\nindex 2a9acf1..0000000\n--- a/setup.cfg\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-[bdist_wheel]\n-universal = 1\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T11:42:31+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/38ebcbdd47f480ced4a7193f4274673388ab3356 - -pyupgrade - -Files changed: -M Products/validation/chain.py -M Products/validation/i18n.py -M Products/validation/tests/test_validation.py -M Products/validation/validators/BaseValidators.py -M Products/validation/validators/EmptyValidator.py -M Products/validation/validators/ExpressionValidator.py -M Products/validation/validators/IdValidator.py -M Products/validation/validators/RangeValidator.py -M Products/validation/validators/RegexValidator.py -M Products/validation/validators/SupplValidators.py - -b'diff --git a/Products/validation/chain.py b/Products/validation/chain.py\nindex 6319feb..ffa6fbb 100644\n--- a/Products/validation/chain.py\n+++ b/Products/validation/chain.py\n@@ -41,7 +41,7 @@ def __repr__(self):\n val = []\n for validator, mode in self:\n name = validator.name\n- val.append("(\'%s\', %s)" % (name, map.get(mode)))\n+ val.append("(\'{}\', {})".format(name, map.get(mode)))\n return \'(%s)\' % \', \'.join(val)\n \n def __len__(self):\n@@ -141,7 +141,7 @@ def __call__(self, value, *args, **kwargs):\n elif mode == V_REQUIRED:\n continue # go on\n else:\n- raise ValidatorError(\'Unknown mode {0}\'.format(mode))\n+ raise ValidatorError(f\'Unknown mode {mode}\')\n else:\n if mode == V_SUFFICIENT:\n if isinstance(result, basestring):\n@@ -155,7 +155,7 @@ def __call__(self, value, *args, **kwargs):\n failed = True\n break # fatal error, stop and fail\n else:\n- raise ValidatorError(\'Unknown mode {0}\'.format(mode))\n+ raise ValidatorError(f\'Unknown mode {mode}\')\n \n if failed:\n return \'\\n\'.join([\ndiff --git a/Products/validation/i18n.py b/Products/validation/i18n.py\nindex f01953c..025cb13 100644\n--- a/Products/validation/i18n.py\n+++ b/Products/validation/i18n.py\n@@ -7,19 +7,8 @@\n \n PloneMessageFactory = MessageFactory(\'plone\')\n \n-if six.PY2:\n- def safe_unicode(value):\n- if isinstance(value, unicode):\n- return value\n- elif isinstance(value, str):\n- try:\n- return unicode(value, \'utf-8\')\n- except UnicodeDecodeError:\n- return unicode(value, \'utf-8\', \'ignore\')\n- return str(value)\n-else:\n- def safe_unicode(value):\n- return value\n+def safe_unicode(value):\n+ return value\n \n \n def recursiveTranslate(message, **kwargs):\ndiff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex ddfb7dc..ef2fba4 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from Products.validation import validation\n \n import doctest\n@@ -6,7 +5,7 @@\n import unittest\n \n \n-class Dummy(object):\n+class Dummy:\n """Dummy object with basic zope-like containment."""\n portal_type = \'dummy\' # needed in Plone 5.2 test\n \n@@ -38,7 +37,7 @@ def __getattr__(self, name, default=None):\n if obj.getId() == name:\n return obj\n return default\n- return super(Dummy, self).__getattr__(name, default)\n+ return super().__getattr__(name, default)\n \n def dummy_checker(self, _id, **kwargs):\n if _id == \'good\':\n@@ -52,28 +51,25 @@ def test_inNumericRange(self):\n v = validation.validatorFor(\'inNumericRange\')\n self.assertEqual(v(10, 1, 20), 1)\n self.assertEqual(v(\'10\', 1, 20), 1)\n- self.assertEqual(v(0, 4, 5), u"Validation failed(inNumericRange): \'0\' out of range(4, 5)")\n+ self.assertEqual(v(0, 4, 5), "Validation failed(inNumericRange): \'0\' out of range(4, 5)")\n \n def test_isDecimal(self):\n v = validation.validatorFor(\'isDecimal\')\n self.assertEqual(v(\'1.5\'), 1)\n self.assertEqual(v(\'1,5\'), 1)\n- self.assertEqual(v(\'NaN\'), u"Validation failed(isDecimal): \'NaN\' is not a decimal number.")\n+ self.assertEqual(v(\'NaN\'), "Validation failed(isDecimal): \'NaN\' is not a decimal number.")\n \n def test_isPrintable(self):\n v = validation.validatorFor(\'isPrintable\')\n self.assertEqual(v(\'text\'), 1)\n self.assertEqual(\n v(\'\\\\u203\'),\n- u"Validation failed(isPrintable): \'\\\\u203\' contains unprintable characters"\n+ "Validation failed(isPrintable): \'\\\\u203\' contains unprintable characters"\n )\n- if six.PY3:\n- int_type = ""\n- else:\n- int_type = ""\n+ int_type = ""\n self.assertEqual(\n v(10),\n- u"Validation failed(isPrintable): 10 of type {0}, expected \'string\'".format(int_type)\n+ f"Validation failed(isPrintable): 10 of type {int_type}, expected \'string\'"\n )\n \n def test_isSSN(self):\n@@ -97,7 +93,7 @@ def test_isURL(self):\n self.assertEqual(v(\'https://be.brussels:8080/manage\'), 1)\n self.assertEqual(v(\'irc://tiran@irc.freenode.net:6667/#plone\'), 1)\n self.assertEqual(v(\'fish://tiran:password@myserver/~/\'), 1)\n- self.assertEqual(v(\'http://\\n\'), u"Validation failed(isURL): \'http://\\n\' is not a valid url.")\n+ self.assertEqual(v(\'http://\\n\'), "Validation failed(isURL): \'http://\\n\' is not a valid url.")\n self.assertNotEqual(v(\'../foo/bar\'), 1)\n \n def test_isEmail(self):\n@@ -105,7 +101,7 @@ def test_isEmail(self):\n self.assertEqual(v(\'test@test.com\'), 1)\n self.assertEqual(v(\'test@be.brussels\'), 1)\n self.assertNotEqual(v(\'@foo.bar\'), 1)\n- self.assertEqual(v(\'me\'), u"Validation failed(isEmail): \'me\' is not a valid email address.")\n+ self.assertEqual(v(\'me\'), "Validation failed(isEmail): \'me\' is not a valid email address.")\n \n def test_isMailto(self):\n v = validation.validatorFor(\'isMailto\')\n@@ -115,7 +111,7 @@ def test_isMailto(self):\n self.assertNotEqual(v(\'mailto:@foo.bar\'), 1)\n self.assertNotEqual(v(\'@foo.bar\'), 1)\n self.assertNotEqual(v(\'mailto:\'), 1)\n- self.assertEqual(v(\'me\'), u"Validation failed(isMailto): \'me\' is not a valid email address.")\n+ self.assertEqual(v(\'me\'), "Validation failed(isMailto): \'me\' is not a valid email address.")\n \n def test_isUnixLikeName(self):\n v = validation.validatorFor(\'isUnixLikeName\')\n@@ -123,7 +119,7 @@ def test_isUnixLikeName(self):\n self.assertTrue(v(\'a_123456\'), 1)\n self.assertNotEqual(v(\'123\'), 1)\n self.assertNotEqual(v(\'ab.c\'), 1)\n- self.assertEqual(v(\'ab,c\'), u"Validation failed(isUnixLikeName): \'ab,c\' this name is not a valid identifier")\n+ self.assertEqual(v(\'ab,c\'), "Validation failed(isUnixLikeName): \'ab,c\' this name is not a valid identifier")\n self.assertNotEqual(v(\'aaaaaaaab\'), 1) # too long\n \n def test_isValidId_basic(self):\n@@ -156,7 +152,7 @@ def test_isValidId_plone(self):\n # This error message would be translated usually, but we do not care.\n self.assertEqual(\n v(\'foo\', obj2),\n- u\'There is already an item named ${name} in this folder.\')\n+ \'There is already an item named ${name} in this folder.\')\n # Plone seems to allow spaces.\n self.assertEqual(v(\'a b\', obj), 1)\n # Some ids are forbidden in Plone. We get an i18n message back.\n@@ -187,7 +183,7 @@ def test_isValidId_fallback(self):\n parent.add(obj)\n self.assertEqual(v(\'good\', obj), 1)\n self.assertEqual(v(\'foo\', obj), 1)\n- self.assertEqual(v(\'a b\', obj), u\'Spaces are not allowed in ids\')\n+ self.assertEqual(v(\'a b\', obj), \'Spaces are not allowed in ids\')\n finally:\n if plone_check_id:\n Products.CMFPlone.utils.check_id = plone_check_id\ndiff --git a/Products/validation/validators/BaseValidators.py b/Products/validation/validators/BaseValidators.py\nindex c956e0c..1f4abb5 100644\n--- a/Products/validation/validators/BaseValidators.py\n+++ b/Products/validation/validators/BaseValidators.py\n@@ -9,41 +9,41 @@\n )\n \n # email re w/o leading \'^\'\n-EMAIL_RE = "([0-9a-zA-Z_&.\'+-]+!)*[0-9a-zA-Z_&.\'+-]+@(([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-z-A-Z])?\\.)+[a-zA-Z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})$"\n+EMAIL_RE = r"([0-9a-zA-Z_&.\'+-]+!)*[0-9a-zA-Z_&.\'+-]+@(([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-z-A-Z])?\\.)+[a-zA-Z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})$"\n \n baseValidators = [\n RangeValidator(\'inNumericRange\', title=\'\', description=\'\'),\n RegexValidator(\'isDecimal\',\n r\'^([+-]?)(?=\\d|(\\.|\\,)\\d)\\d*((\\,|\\.)\\d*)?([Ee]([+-]?\\d+))?$\',\n title=\'\', description=\'\',\n- errmsg=_(u\'is not a decimal number.\')),\n+ errmsg=_(\'is not a decimal number.\')),\n RegexValidator(\'isInt\', r\'^([+-])?\\d+$\', title=\'\', description=\'\',\n- errmsg=_(u\'is not an integer.\')),\n+ errmsg=_(\'is not an integer.\')),\n RegexValidator(\'isPrintable\', r\'[a-zA-Z0-9\\s]+$\', title=\'\', description=\'\',\n- errmsg=_(u\'contains unprintable characters\')),\n+ errmsg=_(\'contains unprintable characters\')),\n RegexValidator(\'isSSN\', r\'^\\d{9}$\', title=\'\', description=\'\',\n- errmsg=_(u\'is not a well formed SSN.\')),\n- RegexValidator(\'isUSPhoneNumber\', r\'^\\d{10}$\', ignore=\'[\\(\\)\\-\\s]\',\n+ errmsg=_(\'is not a well formed SSN.\')),\n+ RegexValidator(\'isUSPhoneNumber\', r\'^\\d{10}$\', ignore=r\'[\\(\\)\\-\\s]\',\n title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid us phone number.\')),\n- RegexValidator(\'isInternationalPhoneNumber\', r\'^\\d+$\', ignore=\'[\\(\\)\\-\\s\\+]\',\n+ errmsg=_(\'is not a valid us phone number.\')),\n+ RegexValidator(\'isInternationalPhoneNumber\', r\'^\\d+$\', ignore=r\'[\\(\\)\\-\\s\\+]\',\n title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid international phone number.\')),\n+ errmsg=_(\'is not a valid international phone number.\')),\n RegexValidator(\'isZipCode\', r\'^(\\d{5}|\\d{9})$\',\n title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid zip code.\')),\n+ errmsg=_(\'is not a valid zip code.\')),\n RegexValidator(\'isURL\', r\'(%s)s?://[^\\s\\r\\n]+\' % \'|\'.join(protocols),\n title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid url.\')),\n+ errmsg=_(\'is not a valid url.\')),\n RegexValidator(\'isEmail\', \'^\'+EMAIL_RE,\n title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid email address.\')),\n+ errmsg=_(\'is not a valid email address.\')),\n RegexValidator(\'isMailto\', \'^mailto:\'+EMAIL_RE,\n title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid email address.\')),\n+ errmsg=_(\'is not a valid email address.\')),\n RegexValidator(\'isUnixLikeName\', r"^[A-Za-z][\\w\\d\\-\\_]{0,7}$",\n title="", description="",\n- errmsg=_(u"this name is not a valid identifier")),\n+ errmsg=_("this name is not a valid identifier")),\n ]\n \n __all__ = (\'baseValidators\', )\ndiff --git a/Products/validation/validators/EmptyValidator.py b/Products/validation/validators/EmptyValidator.py\nindex 681b68e..b558c93 100644\n--- a/Products/validation/validators/EmptyValidator.py\n+++ b/Products/validation/validators/EmptyValidator.py\n@@ -41,7 +41,7 @@ def __call__(self, value, *args, **kwargs):\n return True\n else:\n if getattr(self, \'showError\', False):\n- msg = _(u"Validation failed($name): \'$value\' is not empty.",\n+ msg = _("Validation failed($name): \'$value\' is not empty.",\n mapping = { \'name\' : safe_unicode(self.name), \'value\': safe_unicode(value)})\n return recursiveTranslate(msg, **kwargs)\n else:\ndiff --git a/Products/validation/validators/ExpressionValidator.py b/Products/validation/validators/ExpressionValidator.py\nindex b92a595..63e4fe0 100644\n--- a/Products/validation/validators/ExpressionValidator.py\n+++ b/Products/validation/validators/ExpressionValidator.py\n@@ -90,7 +90,7 @@ def __call__(self, value, instance, *args, **kwargs):\n # support strings as errormsg for backward compatibility\n return self.errormsg % kw\n else:\n- msg = _(u\'validation failed, expr was:$expr\',\n+ msg = _(\'validation failed, expr was:$expr\',\n mapping={\'expr\': safe_unicode(self.expression)})\n return recursiveTranslate(msg, **kwargs)\n \ndiff --git a/Products/validation/validators/IdValidator.py b/Products/validation/validators/IdValidator.py\nindex 1e80a6c..bfd8240 100644\n--- a/Products/validation/validators/IdValidator.py\n+++ b/Products/validation/validators/IdValidator.py\n@@ -14,7 +14,7 @@\n def fallback_check_id(instance, id, **kwargs):\n # space test\n if \' \' in id:\n- msg = _(u\'Spaces are not allowed in ids\')\n+ msg = _(\'Spaces are not allowed in ids\')\n return recursiveTranslate(msg, **kwargs)\n \n # in parent test\n@@ -22,7 +22,7 @@ def fallback_check_id(instance, id, **kwargs):\n # If the id is given to a different object already\n if (id in parent.objectIds() and\n getattr(aq_base(parent), id) is not aq_base(instance)):\n- msg = _(u\'Id $id is already in use\',\n+ msg = _(\'Id $id is already in use\',\n mapping = {\'id\': safe_unicode(id)})\n return recursiveTranslate(msg, **kwargs)\n \ndiff --git a/Products/validation/validators/RangeValidator.py b/Products/validation/validators/RangeValidator.py\nindex 262ffbf..b5c7dc8 100644\n--- a/Products/validation/validators/RangeValidator.py\n+++ b/Products/validation/validators/RangeValidator.py\n@@ -29,13 +29,13 @@ def __call__(self, value, *args, **kwargs):\n try:\n nval = float(value)\n except ValueError:\n- msg = _(u"Validation failed($name): could not convert \'$value\' to number",\n+ msg = _("Validation failed($name): could not convert \'$value\' to number",\n mapping = { \'name\' : safe_unicode(self.name), \'value\': safe_unicode(value)})\n return recursiveTranslate(msg, **kwargs)\n if minval <= nval < maxval:\n return 1\n \n- msg = _(u"Validation failed($name): \'$value\' out of range($min, $max)",\n+ msg = _("Validation failed($name): \'$value\' out of range($min, $max)",\n mapping = {\n \'name\' : safe_unicode(self.name),\n \'value\': safe_unicode(value),\ndiff --git a/Products/validation/validators/RegexValidator.py b/Products/validation/validators/RegexValidator.py\nindex 62923b3..134b2c0 100644\n--- a/Products/validation/validators/RegexValidator.py\n+++ b/Products/validation/validators/RegexValidator.py\n@@ -44,7 +44,7 @@ def __setstate__(self, dict):\n \n def __call__(self, value, *args, **kwargs):\n if not isinstance(value, str):\n- msg = _(u"Validation failed($name): $value of type $type, expected \'string\'",\n+ msg = _("Validation failed($name): $value of type $type, expected \'string\'",\n mapping = {\n \'name\' : safe_unicode(self.name),\n \'value\': safe_unicode(value),\n@@ -62,7 +62,7 @@ def __call__(self, value, *args, **kwargs):\n for r in self.regex:\n m = r.match(value)\n if not m:\n- msg = _(u"Validation failed($name): \'$value\' $errmsg",\n+ msg = _("Validation failed($name): \'$value\' $errmsg",\n mapping={\n \'name\' : safe_unicode(self.name),\n \'value\': safe_unicode(value),\ndiff --git a/Products/validation/validators/SupplValidators.py b/Products/validation/validators/SupplValidators.py\nindex dce96d2..0d7eaad 100644\n--- a/Products/validation/validators/SupplValidators.py\n+++ b/Products/validation/validators/SupplValidators.py\n@@ -85,14 +85,14 @@ def __init__(self, name, title=\'\', description=\'\'):\n \n def __call__(self, value, *args, **kwargs):\n if not value:\n- msg = _(u"Validation failed($name): value is empty ($value).",\n+ msg = _("Validation failed($name): value is empty ($value).",\n mapping = {\'name\': self.name, \'value\': repr(value)})\n return recursiveTranslate(msg, **kwargs)\n if not isinstance(value, DateTime):\n try:\n value = DateTime(value)\n except:\n- msg = _(u"Validation failed($name): could not convert $value to a date.",\n+ msg = _("Validation failed($name): could not convert $value to a date.",\n mapping = {\'name\': safe_unicode(self.name), \'value\': safe_unicode(value)})\n return recursiveTranslate(msg, **kwargs)\n return True\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T11:43:34+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/2a575ed73b12aa21953326a103d42bf6416de0d7 - -Remove six. - -Files changed: -M Products/validation/i18n.py -M Products/validation/service.py -M Products/validation/tests/test_validation.py -M Products/validation/validators/RegexValidator.py -M setup.py - -b"diff --git a/Products/validation/i18n.py b/Products/validation/i18n.py\nindex 025cb13..57a454f 100644\n--- a/Products/validation/i18n.py\n+++ b/Products/validation/i18n.py\n@@ -2,8 +2,6 @@\n from zope.i18n import translate\n from zope.i18nmessageid import Message\n \n-import six\n-\n \n PloneMessageFactory = MessageFactory('plone')\n \ndiff --git a/Products/validation/service.py b/Products/validation/service.py\nindex 898fdd6..9a914ad 100644\n--- a/Products/validation/service.py\n+++ b/Products/validation/service.py\n@@ -5,8 +5,6 @@\n from Products.validation.interfaces.IValidator import IValidator\n from zope.interface import implementer\n \n-import six\n-\n \n @implementer(IValidationService)\n class Service:\ndiff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex ef2fba4..f3f1765 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -1,7 +1,6 @@\n from Products.validation import validation\n \n import doctest\n-import six\n import unittest\n \n \ndiff --git a/Products/validation/validators/RegexValidator.py b/Products/validation/validators/RegexValidator.py\nindex 134b2c0..78b37de 100644\n--- a/Products/validation/validators/RegexValidator.py\n+++ b/Products/validation/validators/RegexValidator.py\n@@ -5,7 +5,6 @@\n from zope.interface import implementer\n \n import re\n-import six\n \n \n def ignoreRE(value, expression):\ndiff --git a/setup.py b/setup.py\nindex 802d690..1d66b6f 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -37,7 +37,6 @@\n extras_require={},\n install_requires=[\n 'setuptools',\n- 'six',\n 'zope.i18n',\n 'zope.i18nmessageid',\n 'zope.interface',\n" - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T11:44:04+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/e2755281184e97a7b2ffe2afb45c763f5921ebf3 - -isort - -Files changed: -M Products/validation/__init__.py -M Products/validation/config.py -M Products/validation/i18n.py -M Products/validation/interfaces/IValidationService.py -M Products/validation/interfaces/IValidator.py -M Products/validation/interfaces/__init__.py -M Products/validation/service.py -M Products/validation/tests/test_validation.py -M Products/validation/validators/BaseValidators.py -M Products/validation/validators/EmptyValidator.py -M Products/validation/validators/ExpressionValidator.py -M Products/validation/validators/IdValidator.py -M Products/validation/validators/RangeValidator.py -M Products/validation/validators/RegexValidator.py -M Products/validation/validators/SupplValidators.py -M Products/validation/validators/__init__.py -M Products/validation/validators/validator.py -M setup.py - -b'diff --git a/Products/validation/__init__.py b/Products/validation/__init__.py\nindex f32bf23..ffad458 100644\n--- a/Products/validation/__init__.py\n+++ b/Products/validation/__init__.py\n@@ -1,7 +1,7 @@\n-from Products.validation.config import validation\n-from Products.validation.chain import ValidationChain\n from Products.validation.chain import V_REQUIRED\n from Products.validation.chain import V_SUFFICIENT\n-from Products.validation.exceptions import UnknowValidatorError\n-from Products.validation.exceptions import FalseValidatorError\n+from Products.validation.chain import ValidationChain\n+from Products.validation.config import validation\n from Products.validation.exceptions import AlreadyRegisteredValidatorError\n+from Products.validation.exceptions import FalseValidatorError\n+from Products.validation.exceptions import UnknowValidatorError\ndiff --git a/Products/validation/config.py b/Products/validation/config.py\nindex 5d4ae44..9b5a7ec 100644\n--- a/Products/validation/config.py\n+++ b/Products/validation/config.py\n@@ -1,6 +1,7 @@\n from Products.validation.validators import initialize\n from Products.validation.ZService import ZService as Service\n \n+\n validation = Service()\n \n initialize(validation)\ndiff --git a/Products/validation/i18n.py b/Products/validation/i18n.py\nindex 57a454f..f15ec1e 100644\n--- a/Products/validation/i18n.py\n+++ b/Products/validation/i18n.py\n@@ -1,6 +1,6 @@\n-from zope.i18nmessageid import MessageFactory\n from zope.i18n import translate\n from zope.i18nmessageid import Message\n+from zope.i18nmessageid import MessageFactory\n \n \n PloneMessageFactory = MessageFactory(\'plone\')\ndiff --git a/Products/validation/interfaces/IValidationService.py b/Products/validation/interfaces/IValidationService.py\nindex 8d9a6b2..f254114 100644\n--- a/Products/validation/interfaces/IValidationService.py\n+++ b/Products/validation/interfaces/IValidationService.py\n@@ -1,4 +1,6 @@\n-from zope.interface import Interface, Attribute\n+from zope.interface import Attribute\n+from zope.interface import Interface\n+\n \n class IValidationService(Interface):\n \ndiff --git a/Products/validation/interfaces/IValidator.py b/Products/validation/interfaces/IValidator.py\nindex 5193765..50c1b29 100644\n--- a/Products/validation/interfaces/IValidator.py\n+++ b/Products/validation/interfaces/IValidator.py\n@@ -1,4 +1,6 @@\n-from zope.interface import Interface, Attribute\n+from zope.interface import Attribute\n+from zope.interface import Interface\n+\n \n class IValidator(Interface):\n \ndiff --git a/Products/validation/interfaces/__init__.py b/Products/validation/interfaces/__init__.py\nindex 28454b5..9d3b313 100644\n--- a/Products/validation/interfaces/__init__.py\n+++ b/Products/validation/interfaces/__init__.py\n@@ -1,3 +1,5 @@\n # backward compatibility\n+from Products.validation.interfaces.IValidationService import (\n+ IValidationService as ivalidationService,\n+)\n from Products.validation.interfaces.IValidator import IValidator as ivalidator\n-from Products.validation.interfaces.IValidationService import IValidationService as ivalidationService\ndiff --git a/Products/validation/service.py b/Products/validation/service.py\nindex 9a914ad..f2dfcff 100644\n--- a/Products/validation/service.py\n+++ b/Products/validation/service.py\n@@ -1,6 +1,6 @@\n+from Products.validation.exceptions import AlreadyRegisteredValidatorError\n from Products.validation.exceptions import FalseValidatorError\n from Products.validation.exceptions import UnknowValidatorError\n-from Products.validation.exceptions import AlreadyRegisteredValidatorError\n from Products.validation.interfaces.IValidationService import IValidationService\n from Products.validation.interfaces.IValidator import IValidator\n from zope.interface import implementer\ndiff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex f3f1765..94c23f8 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -166,6 +166,7 @@ def test_isValidId_plone(self):\n \n def test_isValidId_fallback(self):\n from Products.validation.validators import IdValidator\n+\n # We can only check this if utils.check_id gives an ImportError.\n try:\n from Products.CMFPlone.utils import check_id as plone_check_id\n@@ -189,7 +190,8 @@ def test_isValidId_fallback(self):\n \n \n def test_suite():\n- from unittest import TestSuite, makeSuite\n+ from unittest import makeSuite\n+ from unittest import TestSuite\n suite = TestSuite()\n suite.addTest(makeSuite(TestValidation))\n \ndiff --git a/Products/validation/validators/BaseValidators.py b/Products/validation/validators/BaseValidators.py\nindex 1f4abb5..54aaa0e 100644\n--- a/Products/validation/validators/BaseValidators.py\n+++ b/Products/validation/validators/BaseValidators.py\n@@ -1,6 +1,7 @@\n-from Products.validation.validators.RegexValidator import RegexValidator\n-from Products.validation.validators.RangeValidator import RangeValidator\n from Products.validation.i18n import PloneMessageFactory as _\n+from Products.validation.validators.RangeValidator import RangeValidator\n+from Products.validation.validators.RegexValidator import RegexValidator\n+\n \n # protocols for isURL validator, the secure (*s) variants are automagically\n # added\ndiff --git a/Products/validation/validators/EmptyValidator.py b/Products/validation/validators/EmptyValidator.py\nindex b558c93..e4257d8 100644\n--- a/Products/validation/validators/EmptyValidator.py\n+++ b/Products/validation/validators/EmptyValidator.py\n@@ -1,8 +1,9 @@\n-from zope.interface import implementer\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n from Products.validation.interfaces.IValidator import IValidator\n+from zope.interface import implementer\n+\n \n _marker = []\n \ndiff --git a/Products/validation/validators/ExpressionValidator.py b/Products/validation/validators/ExpressionValidator.py\nindex 63e4fe0..d75382c 100644\n--- a/Products/validation/validators/ExpressionValidator.py\n+++ b/Products/validation/validators/ExpressionValidator.py\n@@ -21,13 +21,14 @@\n """\n """\n \n-from Products.validation.interfaces.IValidator import IValidator\n+from Products.PageTemplates.Expressions import getEngine\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n-from Products.PageTemplates.Expressions import getEngine\n-from zope.interface import implementer\n+from Products.validation.interfaces.IValidator import IValidator\n from zope.i18nmessageid import Message\n+from zope.interface import implementer\n+\n \n @implementer(IValidator)\n class ExpressionValidator:\ndiff --git a/Products/validation/validators/IdValidator.py b/Products/validation/validators/IdValidator.py\nindex bfd8240..7d2dde7 100644\n--- a/Products/validation/validators/IdValidator.py\n+++ b/Products/validation/validators/IdValidator.py\n@@ -1,14 +1,14 @@\n-from Acquisition import aq_parent\n-from Acquisition import aq_inner\n from Acquisition import aq_base\n from Acquisition import aq_get\n-from zExceptions import BadRequest\n+from Acquisition import aq_inner\n+from Acquisition import aq_parent\n from OFS import ObjectManager\n-from Products.validation.interfaces.IValidator import IValidator\n-from zope.interface import implementer\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n+from zExceptions import BadRequest\n+from zope.interface import implementer\n \n \n def fallback_check_id(instance, id, **kwargs):\ndiff --git a/Products/validation/validators/RangeValidator.py b/Products/validation/validators/RangeValidator.py\nindex b5c7dc8..33e644f 100644\n--- a/Products/validation/validators/RangeValidator.py\n+++ b/Products/validation/validators/RangeValidator.py\n@@ -1,8 +1,9 @@\n-from Products.validation.interfaces.IValidator import IValidator\n-from zope.interface import implementer\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n+from zope.interface import implementer\n+\n \n @implementer(IValidator)\n class RangeValidator:\ndiff --git a/Products/validation/validators/RegexValidator.py b/Products/validation/validators/RegexValidator.py\nindex 78b37de..36e6493 100644\n--- a/Products/validation/validators/RegexValidator.py\n+++ b/Products/validation/validators/RegexValidator.py\n@@ -1,7 +1,7 @@\n-from Products.validation.interfaces.IValidator import IValidator\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n from zope.interface import implementer\n \n import re\ndiff --git a/Products/validation/validators/SupplValidators.py b/Products/validation/validators/SupplValidators.py\nindex 0d7eaad..a08218d 100644\n--- a/Products/validation/validators/SupplValidators.py\n+++ b/Products/validation/validators/SupplValidators.py\n@@ -1,12 +1,12 @@\n from Acquisition import aq_base\n from DateTime import DateTime\n-from ZPublisher.HTTPRequest import FileUpload\n-\n-from Products.validation.interfaces.IValidator import IValidator\n-from zope.interface import implementer\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n+from zope.interface import implementer\n+from ZPublisher.HTTPRequest import FileUpload\n+\n \n _marker = []\n \ndiff --git a/Products/validation/validators/__init__.py b/Products/validation/validators/__init__.py\nindex f6abbfa..c688dae 100644\n--- a/Products/validation/validators/__init__.py\n+++ b/Products/validation/validators/__init__.py\n@@ -1,19 +1,28 @@\n-from Products.validation.validators.RegexValidator import RegexValidator\n-from Products.validation.validators.RangeValidator import RangeValidator\n from Products.validation.validators.ExpressionValidator import ExpressionValidator\n+from Products.validation.validators.RangeValidator import RangeValidator\n+from Products.validation.validators.RegexValidator import RegexValidator\n+\n \n validators = []\n \n from Products.validation.validators.BaseValidators import baseValidators\n+\n+\n validators.extend(baseValidators)\n \n from Products.validation.validators.EmptyValidator import validatorList\n+\n+\n validators.extend(validatorList)\n \n from Products.validation.validators.SupplValidators import validatorList\n+\n+\n validators.extend(validatorList)\n \n from Products.validation.validators.IdValidator import validatorList\n+\n+\n validators.extend(validatorList)\n \n \ndiff --git a/Products/validation/validators/validator.py b/Products/validation/validators/validator.py\nindex 5919568..6dcaee3 100644\n--- a/Products/validation/validators/validator.py\n+++ b/Products/validation/validators/validator.py\n@@ -1,3 +1,3 @@\n # backward compatibility\n-from RegexValidator import RegexValidator\n from RangeValidator import RangeValidator\n+from RegexValidator import RegexValidator\ndiff --git a/setup.py b/setup.py\nindex 1d66b6f..76a1a6a 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -1,4 +1,6 @@\n-from setuptools import setup, find_packages\n+from setuptools import find_packages\n+from setuptools import setup\n+\n \n version = \'2.1.4.dev0\'\n \n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T11:45:02+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/d8b57261c968a1e82a73b99a43dadd3f268fa529 - -black - -Files changed: -M Products/__init__.py -M Products/validation/ZService.py -M Products/validation/chain.py -M Products/validation/exceptions.py -M Products/validation/i18n.py -M Products/validation/interfaces/IValidationService.py -M Products/validation/interfaces/IValidator.py -M Products/validation/service.py -M Products/validation/tests/__init__.py -M Products/validation/tests/test_validation.py -M Products/validation/validators/BaseValidators.py -M Products/validation/validators/EmptyValidator.py -M Products/validation/validators/ExpressionValidator.py -M Products/validation/validators/IdValidator.py -M Products/validation/validators/RangeValidator.py -M Products/validation/validators/RegexValidator.py -M Products/validation/validators/SupplValidators.py -M setup.py - -b'diff --git a/Products/__init__.py b/Products/__init__.py\nindex f48ad10..05f0beb 100644\n--- a/Products/__init__.py\n+++ b/Products/__init__.py\n@@ -1,6 +1,7 @@\n # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages\n try:\n- __import__(\'pkg_resources\').declare_namespace(__name__)\n+ __import__("pkg_resources").declare_namespace(__name__)\n except ImportError:\n from pkgutil import extend_path\n+\n __path__ = extend_path(__path__, __name__)\ndiff --git a/Products/validation/ZService.py b/Products/validation/ZService.py\nindex c3aefc1..c3e8ced 100644\n--- a/Products/validation/ZService.py\n+++ b/Products/validation/ZService.py\n@@ -6,16 +6,18 @@\n \n \n # make validator service public\n-security = ModuleSecurityInfo(\'Products.validation.config\')\n-security.declarePublic(\'validation\')\n+security = ModuleSecurityInfo("Products.validation.config")\n+security.declarePublic("validation")\n+\n \n class ZService(Service, Implicit):\n """Service running in a zope site - exposes some methods"""\n \n security = ClassSecurityInfo()\n \n- security.declarePublic(\'validate\')\n- security.declarePublic(\'__call__\')\n- security.declarePublic(\'validatorFor\')\n+ security.declarePublic("validate")\n+ security.declarePublic("__call__")\n+ security.declarePublic("validatorFor")\n+\n \n InitializeClass(ZService)\ndiff --git a/Products/validation/chain.py b/Products/validation/chain.py\nindex ffa6fbb..c11d76a 100644\n--- a/Products/validation/chain.py\n+++ b/Products/validation/chain.py\n@@ -4,19 +4,17 @@\n from zope.interface import implementer\n \n \n-V_REQUIRED = 1\n+V_REQUIRED = 1\n V_SUFFICIENT = 2\n-TupleType = type((0,1))\n+TupleType = type((0, 1))\n ListType = type([])\n \n \n @implementer(IValidationChain)\n class ValidationChain:\n- """\n- """\n+ """ """\n \n- def __init__(self, name, title=\'\', description=\'\', validators=(),\n- register=False):\n+ def __init__(self, name, title="", description="", validators=(), register=False):\n self.name = name\n self.title = title or name\n self.description = description\n@@ -24,7 +22,7 @@ def __init__(self, name, title=\'\', description=\'\', validators=(),\n self._chain = []\n \n if type(validators) not in (TupleType, ListType):\n- validators = (validators, )\n+ validators = (validators,)\n for validator in validators:\n if type(validator) in (TupleType, ListType):\n self.append(validator[0], validator[1])\n@@ -35,23 +33,20 @@ def __init__(self, name, title=\'\', description=\'\', validators=(),\n validationService.register(self)\n \n def __repr__(self):\n- """print obj support\n- """\n- map = { V_REQUIRED : \'V_REQUIRED\', V_SUFFICIENT : \'V_SUFFICIENT\' }\n+ """print obj support"""\n+ map = {V_REQUIRED: "V_REQUIRED", V_SUFFICIENT: "V_SUFFICIENT"}\n val = []\n for validator, mode in self:\n name = validator.name\n val.append("(\'{}\', {})".format(name, map.get(mode)))\n- return \'(%s)\' % \', \'.join(val)\n+ return "(%s)" % ", ".join(val)\n \n def __len__(self):\n- """len(obj) support\n- """\n+ """len(obj) support"""\n return len(self._chain)\n \n def __iter__(self):\n- """Python 2.3 for i in x support\n- """\n+ """Python 2.3 for i in x support"""\n return iter(zip(self._chain, self._v_mode))\n \n def __cmp__(self, key):\n@@ -62,45 +57,37 @@ def __cmp__(self, key):\n return cmp(repr(self), str)\n \n def __getitem__(self, idx):\n- """self[idx] support and Python 2.1 for i in x support\n- """\n+ """self[idx] support and Python 2.1 for i in x support"""\n return self._chain[idx], self._v_mode[idx]\n \n def append(self, id_or_obj, mode=V_REQUIRED):\n- """Appends a validator\n- """\n+ """Appends a validator"""\n validator = self.setValidator(id_or_obj)\n self.setMode(validator, mode)\n \n def appendRequired(self, id_or_obj):\n- """Appends a validator as required\n- """\n+ """Appends a validator as required"""\n self.append(id_or_obj, mode=V_REQUIRED)\n \n def appendSufficient(self, id_or_obj):\n- """Appends a validator as sufficient\n- """\n+ """Appends a validator as sufficient"""\n self.append(id_or_obj, mode=V_SUFFICIENT)\n \n def insert(self, id_or_obj, mode=V_REQUIRED, position=0):\n- """Inserts a validator at position (default 0)\n- """\n+ """Inserts a validator at position (default 0)"""\n validator = self.setValidator(id_or_obj, position=position)\n self.setMode(validator, mode, position=position)\n \n def insertRequired(self, id_or_obj, position=0):\n- """Inserts a validator as required at position (default 0)\n- """\n+ """Inserts a validator as required at position (default 0)"""\n self.insert(id_or_obj, mode=V_REQUIRED, position=0)\n \n def insertSufficient(self, id_or_obj, position=0):\n- """Inserts a validator as sufficient at position (default 0)\n- """\n+ """Inserts a validator as sufficient at position (default 0)"""\n self.insert(id_or_obj, mode=V_SUFFICIENT, position=0)\n \n def setMode(self, validator, mode, position=None):\n- """Set mode\n- """\n+ """Set mode"""\n # validator not required\n if position is None:\n self._v_mode.append(mode)\n@@ -108,8 +95,7 @@ def setMode(self, validator, mode, position=None):\n self._v_mode.insert(position, mode)\n \n def setValidator(self, id_or_obj, position=None):\n- """Set validator\n- """\n+ """Set validator"""\n validator = validationService.validatorFor(id_or_obj)\n \n if position is None:\n@@ -120,8 +106,7 @@ def setValidator(self, id_or_obj, position=None):\n return validator\n \n def __call__(self, value, *args, **kwargs):\n- """Do validation\n- """\n+ """Do validation"""\n results = {}\n failed = False\n if len(self) == 1:\n@@ -130,38 +115,40 @@ def __call__(self, value, *args, **kwargs):\n # There is only one validator and its mode is\n # \'sufficient\' which means it does not have to\n # validate. So we cut the validation short.\n- return True # validation was successful\n+ return True # validation was successful\n \n for validator, mode in self:\n name = validator.name\n result = validator(value, *args, **kwargs)\n if result == True:\n if mode == V_SUFFICIENT:\n- return True # validation was successful\n+ return True # validation was successful\n elif mode == V_REQUIRED:\n- continue # go on\n+ continue # go on\n else:\n- raise ValidatorError(f\'Unknown mode {mode}\')\n+ raise ValidatorError(f"Unknown mode {mode}")\n else:\n if mode == V_SUFFICIENT:\n if isinstance(result, basestring):\n # don\'t log if validator doesn\'t return an error msg\n results[name] = result\n- continue # no fatal error, go on\n+ continue # no fatal error, go on\n elif mode == V_REQUIRED:\n if isinstance(result, basestring):\n # don\'t log if validator doesn\'t return an error msg\n results[name] = result\n failed = True\n- break # fatal error, stop and fail\n+ break # fatal error, stop and fail\n else:\n- raise ValidatorError(f\'Unknown mode {mode}\')\n+ raise ValidatorError(f"Unknown mode {mode}")\n \n if failed:\n- return \'\\n\'.join([\n- #\'%s: %s\' % (name, res)\n- \'%s\' % res\n- for name, res in results.items()]\n- )\n+ return "\\n".join(\n+ [\n+ #\'%s: %s\' % (name, res)\n+ "%s" % res\n+ for name, res in results.items()\n+ ]\n+ )\n else:\n return True\ndiff --git a/Products/validation/exceptions.py b/Products/validation/exceptions.py\nindex 1e7b79f..eb2ab8d 100644\n--- a/Products/validation/exceptions.py\n+++ b/Products/validation/exceptions.py\n@@ -1,4 +1,14 @@\n-class ValidatorError(Exception): pass\n-class UnknowValidatorError(ValidatorError): pass\n-class FalseValidatorError(ValidatorError): pass\n-class AlreadyRegisteredValidatorError(ValidatorError): pass\n\\ No newline at end of file\n+class ValidatorError(Exception):\n+ pass\n+\n+\n+class UnknowValidatorError(ValidatorError):\n+ pass\n+\n+\n+class FalseValidatorError(ValidatorError):\n+ pass\n+\n+\n+class AlreadyRegisteredValidatorError(ValidatorError):\n+ pass\ndiff --git a/Products/validation/i18n.py b/Products/validation/i18n.py\nindex f15ec1e..6133ea7 100644\n--- a/Products/validation/i18n.py\n+++ b/Products/validation/i18n.py\n@@ -3,7 +3,8 @@\n from zope.i18nmessageid import MessageFactory\n \n \n-PloneMessageFactory = MessageFactory(\'plone\')\n+PloneMessageFactory = MessageFactory("plone")\n+\n \n def safe_unicode(value):\n return value\n@@ -14,7 +15,7 @@ def recursiveTranslate(message, **kwargs):\n if kwargs[\'REQUEST\'] is None, return the message untranslated\n """\n \n- request = kwargs.get(\'REQUEST\',None)\n+ request = kwargs.get("REQUEST", None)\n \n map = message.mapping\n if map:\ndiff --git a/Products/validation/interfaces/IValidationService.py b/Products/validation/interfaces/IValidationService.py\nindex f254114..a2fc596 100644\n--- a/Products/validation/interfaces/IValidationService.py\n+++ b/Products/validation/interfaces/IValidationService.py\n@@ -14,4 +14,4 @@ def register(validator):\n """load a validator for access by name"""\n \n def unregister(name_or_validator):\n- """unregisters a validator by name"""\n\\ No newline at end of file\n+ """unregisters a validator by name"""\ndiff --git a/Products/validation/interfaces/IValidator.py b/Products/validation/interfaces/IValidator.py\nindex 50c1b29..d890f26 100644\n--- a/Products/validation/interfaces/IValidator.py\n+++ b/Products/validation/interfaces/IValidator.py\n@@ -13,5 +13,4 @@ def __call__(value, *args, **kwargs):\n \n \n class IValidationChain(IValidator):\n- """Marker interface for a chain\n- """\n+ """Marker interface for a chain"""\ndiff --git a/Products/validation/service.py b/Products/validation/service.py\nindex f2dfcff..6aaa368 100644\n--- a/Products/validation/service.py\n+++ b/Products/validation/service.py\n@@ -29,7 +29,7 @@ def validatorFor(self, name_or_validator):\n else:\n raise FalseValidatorError(name_or_validator)\n \n- def register(self, validator): #XXX\n+ def register(self, validator): # XXX\n if not IValidator.providedBy(validator):\n raise FalseValidatorError(validator)\n name = validator.name\ndiff --git a/Products/validation/tests/__init__.py b/Products/validation/tests/__init__.py\nindex d310fdd..5bb534f 100644\n--- a/Products/validation/tests/__init__.py\n+++ b/Products/validation/tests/__init__.py\n@@ -1 +1 @@\n-# package\n\\ No newline at end of file\n+# package\ndiff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex 94c23f8..060f862 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -6,7 +6,8 @@\n \n class Dummy:\n """Dummy object with basic zope-like containment."""\n- portal_type = \'dummy\' # needed in Plone 5.2 test\n+\n+ portal_type = "dummy" # needed in Plone 5.2 test\n \n def __init__(self, _id=None):\n self.id = _id\n@@ -39,130 +40,143 @@ def __getattr__(self, name, default=None):\n return super().__getattr__(name, default)\n \n def dummy_checker(self, _id, **kwargs):\n- if _id == \'good\':\n+ if _id == "good":\n return 1\n- return \'bad id\'\n+ return "bad id"\n \n \n class TestValidation(unittest.TestCase):\n \n def test_inNumericRange(self):\n- v = validation.validatorFor(\'inNumericRange\')\n+ v = validation.validatorFor("inNumericRange")\n self.assertEqual(v(10, 1, 20), 1)\n- self.assertEqual(v(\'10\', 1, 20), 1)\n- self.assertEqual(v(0, 4, 5), "Validation failed(inNumericRange): \'0\' out of range(4, 5)")\n+ self.assertEqual(v("10", 1, 20), 1)\n+ self.assertEqual(\n+ v(0, 4, 5), "Validation failed(inNumericRange): \'0\' out of range(4, 5)"\n+ )\n \n def test_isDecimal(self):\n- v = validation.validatorFor(\'isDecimal\')\n- self.assertEqual(v(\'1.5\'), 1)\n- self.assertEqual(v(\'1,5\'), 1)\n- self.assertEqual(v(\'NaN\'), "Validation failed(isDecimal): \'NaN\' is not a decimal number.")\n+ v = validation.validatorFor("isDecimal")\n+ self.assertEqual(v("1.5"), 1)\n+ self.assertEqual(v("1,5"), 1)\n+ self.assertEqual(\n+ v("NaN"), "Validation failed(isDecimal): \'NaN\' is not a decimal number."\n+ )\n \n def test_isPrintable(self):\n- v = validation.validatorFor(\'isPrintable\')\n- self.assertEqual(v(\'text\'), 1)\n+ v = validation.validatorFor("isPrintable")\n+ self.assertEqual(v("text"), 1)\n self.assertEqual(\n- v(\'\\\\u203\'),\n- "Validation failed(isPrintable): \'\\\\u203\' contains unprintable characters"\n+ v("\\\\u203"),\n+ "Validation failed(isPrintable): \'\\\\u203\' contains unprintable characters",\n )\n int_type = ""\n self.assertEqual(\n v(10),\n- f"Validation failed(isPrintable): 10 of type {int_type}, expected \'string\'"\n+ f"Validation failed(isPrintable): 10 of type {int_type}, expected \'string\'",\n )\n \n def test_isSSN(self):\n- v = validation.validatorFor(\'isSSN\')\n- self.assertEqual(v(\'111223333\'), 1)\n- self.assertEqual(v(\'111-22-3333\', ignore=r\'-\'), 1)\n+ v = validation.validatorFor("isSSN")\n+ self.assertEqual(v("111223333"), 1)\n+ self.assertEqual(v("111-22-3333", ignore=r"-"), 1)\n \n def test_isUSPhoneNumber(self):\n- v = validation.validatorFor(\'isUSPhoneNumber\')\n- self.assertEqual(v(\'(212) 555-1212\',\n- ignore=r\'[\\s\\(\\)\\-]\'), 1)\n- self.assertEqual(v(\'2125551212\',\n- ignore=r\'[\\s\\(\\)\\-]\'), 1)\n+ v = validation.validatorFor("isUSPhoneNumber")\n+ self.assertEqual(v("(212) 555-1212", ignore=r"[\\s\\(\\)\\-]"), 1)\n+ self.assertEqual(v("2125551212", ignore=r"[\\s\\(\\)\\-]"), 1)\n \n- self.assertEqual(v(\'(212) 555-1212\'), 1)\n+ self.assertEqual(v("(212) 555-1212"), 1)\n \n def test_isURL(self):\n- v = validation.validatorFor(\'isURL\')\n- self.assertEqual(v(\'http://foo.bar:8080/manage\'), 1)\n- self.assertEqual(v(\'https://foo.bar:8080/manage\'), 1)\n- self.assertEqual(v(\'https://be.brussels:8080/manage\'), 1)\n- self.assertEqual(v(\'irc://tiran@irc.freenode.net:6667/#plone\'), 1)\n- self.assertEqual(v(\'fish://tiran:password@myserver/~/\'), 1)\n- self.assertEqual(v(\'http://\\n\'), "Validation failed(isURL): \'http://\\n\' is not a valid url.")\n- self.assertNotEqual(v(\'../foo/bar\'), 1)\n+ v = validation.validatorFor("isURL")\n+ self.assertEqual(v("http://foo.bar:8080/manage"), 1)\n+ self.assertEqual(v("https://foo.bar:8080/manage"), 1)\n+ self.assertEqual(v("https://be.brussels:8080/manage"), 1)\n+ self.assertEqual(v("irc://tiran@irc.freenode.net:6667/#plone"), 1)\n+ self.assertEqual(v("fish://tiran:password@myserver/~/"), 1)\n+ self.assertEqual(\n+ v("http://\\n"), "Validation failed(isURL): \'http://\\n\' is not a valid url."\n+ )\n+ self.assertNotEqual(v("../foo/bar"), 1)\n \n def test_isEmail(self):\n- v = validation.validatorFor(\'isEmail\')\n- self.assertEqual(v(\'test@test.com\'), 1)\n- self.assertEqual(v(\'test@be.brussels\'), 1)\n- self.assertNotEqual(v(\'@foo.bar\'), 1)\n- self.assertEqual(v(\'me\'), "Validation failed(isEmail): \'me\' is not a valid email address.")\n+ v = validation.validatorFor("isEmail")\n+ self.assertEqual(v("test@test.com"), 1)\n+ self.assertEqual(v("test@be.brussels"), 1)\n+ self.assertNotEqual(v("@foo.bar"), 1)\n+ self.assertEqual(\n+ v("me"), "Validation failed(isEmail): \'me\' is not a valid email address."\n+ )\n \n def test_isMailto(self):\n- v = validation.validatorFor(\'isMailto\')\n- self.assertEqual(v(\'mailto:test@test.com\'), 1)\n- self.assertEqual(v(\'mailto:test@be.brussels\'), 1)\n- self.assertNotEqual(v(\'test@test.com\'), 1)\n- self.assertNotEqual(v(\'mailto:@foo.bar\'), 1)\n- self.assertNotEqual(v(\'@foo.bar\'), 1)\n- self.assertNotEqual(v(\'mailto:\'), 1)\n- self.assertEqual(v(\'me\'), "Validation failed(isMailto): \'me\' is not a valid email address.")\n+ v = validation.validatorFor("isMailto")\n+ self.assertEqual(v("mailto:test@test.com"), 1)\n+ self.assertEqual(v("mailto:test@be.brussels"), 1)\n+ self.assertNotEqual(v("test@test.com"), 1)\n+ self.assertNotEqual(v("mailto:@foo.bar"), 1)\n+ self.assertNotEqual(v("@foo.bar"), 1)\n+ self.assertNotEqual(v("mailto:"), 1)\n+ self.assertEqual(\n+ v("me"), "Validation failed(isMailto): \'me\' is not a valid email address."\n+ )\n \n def test_isUnixLikeName(self):\n- v = validation.validatorFor(\'isUnixLikeName\')\n- self.assertEqual(v(\'abcd\'), 1)\n- self.assertTrue(v(\'a_123456\'), 1)\n- self.assertNotEqual(v(\'123\'), 1)\n- self.assertNotEqual(v(\'ab.c\'), 1)\n- self.assertEqual(v(\'ab,c\'), "Validation failed(isUnixLikeName): \'ab,c\' this name is not a valid identifier")\n- self.assertNotEqual(v(\'aaaaaaaab\'), 1) # too long\n+ v = validation.validatorFor("isUnixLikeName")\n+ self.assertEqual(v("abcd"), 1)\n+ self.assertTrue(v("a_123456"), 1)\n+ self.assertNotEqual(v("123"), 1)\n+ self.assertNotEqual(v("ab.c"), 1)\n+ self.assertEqual(\n+ v("ab,c"),\n+ "Validation failed(isUnixLikeName): \'ab,c\' this name is not a valid identifier",\n+ )\n+ self.assertNotEqual(v("aaaaaaaab"), 1) # too long\n \n def test_isValidId_basic(self):\n from Products.validation.validators import IdValidator\n \n- v = validation.validatorFor(\'isValidId\')\n- obj = Dummy(\'foo\')\n+ v = validation.validatorFor("isValidId")\n+ obj = Dummy("foo")\n \n # Use a specific checker.\n obj.check_id = obj.dummy_checker\n- self.assertEqual(v(\'good\', obj), 1)\n- self.assertEqual(v(\'a b\', obj), \'bad id\')\n+ self.assertEqual(v("good", obj), 1)\n+ self.assertEqual(v("a b", obj), "bad id")\n \n def test_isValidId_plone(self):\n from Products.validation.validators import IdValidator\n+\n try:\n from Products.CMFPlone.utils import check_id\n except ImportError:\n return\n \n- v = validation.validatorFor(\'isValidId\')\n- obj = Dummy(\'foo\')\n- obj2 = Dummy(\'foo2\')\n- parent = Dummy(\'parent\')\n+ v = validation.validatorFor("isValidId")\n+ obj = Dummy("foo")\n+ obj2 = Dummy("foo2")\n+ parent = Dummy("parent")\n parent.add(obj)\n parent.add(obj2)\n \n- self.assertEqual(v(\'good\', obj), 1)\n- self.assertEqual(v(\'foo\', obj), 1)\n+ self.assertEqual(v("good", obj), 1)\n+ self.assertEqual(v("foo", obj), 1)\n # This error message would be translated usually, but we do not care.\n self.assertEqual(\n- v(\'foo\', obj2),\n- \'There is already an item named ${name} in this folder.\')\n+ v("foo", obj2), "There is already an item named ${name} in this folder."\n+ )\n # Plone seems to allow spaces.\n- self.assertEqual(v(\'a b\', obj), 1)\n+ self.assertEqual(v("a b", obj), 1)\n # Some ids are forbidden in Plone. We get an i18n message back.\n # Problem: on Plone 5.1, utils.check_id simply looks for a\n # check_id script/attribute on the context. This will fail.\n # So only test this in Plone 5.2+, not on 5.1.\n import pkg_resources\n- version = pkg_resources.get_distribution(\'Products.CMFPlone\').version\n- if version.startswith(\'5.1\'):\n+\n+ version = pkg_resources.get_distribution("Products.CMFPlone").version\n+ if version.startswith("5.1"):\n return\n- self.assertEqual(v(\'layout\', obj), \'${name} is reserved.\')\n+ self.assertEqual(v("layout", obj), "${name} is reserved.")\n \n def test_isValidId_fallback(self):\n from Products.validation.validators import IdValidator\n@@ -174,16 +188,17 @@ def test_isValidId_fallback(self):\n plone_check_id = None\n else:\n import Products.CMFPlone.utils\n+\n del Products.CMFPlone.utils.check_id\n \n try:\n- v = validation.validatorFor(\'isValidId\')\n- obj = Dummy(\'foo\')\n- parent = Dummy(\'parent\')\n+ v = validation.validatorFor("isValidId")\n+ obj = Dummy("foo")\n+ parent = Dummy("parent")\n parent.add(obj)\n- self.assertEqual(v(\'good\', obj), 1)\n- self.assertEqual(v(\'foo\', obj), 1)\n- self.assertEqual(v(\'a b\', obj), \'Spaces are not allowed in ids\')\n+ self.assertEqual(v("good", obj), 1)\n+ self.assertEqual(v("foo", obj), 1)\n+ self.assertEqual(v("a b", obj), "Spaces are not allowed in ids")\n finally:\n if plone_check_id:\n Products.CMFPlone.utils.check_id = plone_check_id\n@@ -192,12 +207,11 @@ def test_isValidId_fallback(self):\n def test_suite():\n from unittest import makeSuite\n from unittest import TestSuite\n+\n suite = TestSuite()\n suite.addTest(makeSuite(TestValidation))\n \n- doctests = (\n- \'Products.validation.validators.ExpressionValidator\',\n- )\n+ doctests = ("Products.validation.validators.ExpressionValidator",)\n for module in doctests:\n suite.addTest(doctest.DocTestSuite(module))\n \ndiff --git a/Products/validation/validators/BaseValidators.py b/Products/validation/validators/BaseValidators.py\nindex 54aaa0e..0d1fb24 100644\n--- a/Products/validation/validators/BaseValidators.py\n+++ b/Products/validation/validators/BaseValidators.py\n@@ -5,46 +5,109 @@\n \n # protocols for isURL validator, the secure (*s) variants are automagically\n # added\n-protocols = (\'http\', \'ftp\', \'irc\', \'news\', \'imap\', \'gopher\', \'jabber\',\n- \'webdav\', \'smb\', \'fish\', \'ldap\', \'pop3\', \'smtp\', \'sftp\', \'ssh\', \'feed\'\n- )\n+protocols = (\n+ "http",\n+ "ftp",\n+ "irc",\n+ "news",\n+ "imap",\n+ "gopher",\n+ "jabber",\n+ "webdav",\n+ "smb",\n+ "fish",\n+ "ldap",\n+ "pop3",\n+ "smtp",\n+ "sftp",\n+ "ssh",\n+ "feed",\n+)\n \n # email re w/o leading \'^\'\n EMAIL_RE = r"([0-9a-zA-Z_&.\'+-]+!)*[0-9a-zA-Z_&.\'+-]+@(([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-z-A-Z])?\\.)+[a-zA-Z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})$"\n \n baseValidators = [\n- RangeValidator(\'inNumericRange\', title=\'\', description=\'\'),\n- RegexValidator(\'isDecimal\',\n- r\'^([+-]?)(?=\\d|(\\.|\\,)\\d)\\d*((\\,|\\.)\\d*)?([Ee]([+-]?\\d+))?$\',\n- title=\'\', description=\'\',\n- errmsg=_(\'is not a decimal number.\')),\n- RegexValidator(\'isInt\', r\'^([+-])?\\d+$\', title=\'\', description=\'\',\n- errmsg=_(\'is not an integer.\')),\n- RegexValidator(\'isPrintable\', r\'[a-zA-Z0-9\\s]+$\', title=\'\', description=\'\',\n- errmsg=_(\'contains unprintable characters\')),\n- RegexValidator(\'isSSN\', r\'^\\d{9}$\', title=\'\', description=\'\',\n- errmsg=_(\'is not a well formed SSN.\')),\n- RegexValidator(\'isUSPhoneNumber\', r\'^\\d{10}$\', ignore=r\'[\\(\\)\\-\\s]\',\n- title=\'\', description=\'\',\n- errmsg=_(\'is not a valid us phone number.\')),\n- RegexValidator(\'isInternationalPhoneNumber\', r\'^\\d+$\', ignore=r\'[\\(\\)\\-\\s\\+]\',\n- title=\'\', description=\'\',\n- errmsg=_(\'is not a valid international phone number.\')),\n- RegexValidator(\'isZipCode\', r\'^(\\d{5}|\\d{9})$\',\n- title=\'\', description=\'\',\n- errmsg=_(\'is not a valid zip code.\')),\n- RegexValidator(\'isURL\', r\'(%s)s?://[^\\s\\r\\n]+\' % \'|\'.join(protocols),\n- title=\'\', description=\'\',\n- errmsg=_(\'is not a valid url.\')),\n- RegexValidator(\'isEmail\', \'^\'+EMAIL_RE,\n- title=\'\', description=\'\',\n- errmsg=_(\'is not a valid email address.\')),\n- RegexValidator(\'isMailto\', \'^mailto:\'+EMAIL_RE,\n- title=\'\', description=\'\',\n- errmsg=_(\'is not a valid email address.\')),\n- RegexValidator(\'isUnixLikeName\', r"^[A-Za-z][\\w\\d\\-\\_]{0,7}$",\n- title="", description="",\n- errmsg=_("this name is not a valid identifier")),\n- ]\n+ RangeValidator("inNumericRange", title="", description=""),\n+ RegexValidator(\n+ "isDecimal",\n+ r"^([+-]?)(?=\\d|(\\.|\\,)\\d)\\d*((\\,|\\.)\\d*)?([Ee]([+-]?\\d+))?$",\n+ title="",\n+ description="",\n+ errmsg=_("is not a decimal number."),\n+ ),\n+ RegexValidator(\n+ "isInt",\n+ r"^([+-])?\\d+$",\n+ title="",\n+ description="",\n+ errmsg=_("is not an integer."),\n+ ),\n+ RegexValidator(\n+ "isPrintable",\n+ r"[a-zA-Z0-9\\s]+$",\n+ title="",\n+ description="",\n+ errmsg=_("contains unprintable characters"),\n+ ),\n+ RegexValidator(\n+ "isSSN",\n+ r"^\\d{9}$",\n+ title="",\n+ description="",\n+ errmsg=_("is not a well formed SSN."),\n+ ),\n+ RegexValidator(\n+ "isUSPhoneNumber",\n+ r"^\\d{10}$",\n+ ignore=r"[\\(\\)\\-\\s]",\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid us phone number."),\n+ ),\n+ RegexValidator(\n+ "isInternationalPhoneNumber",\n+ r"^\\d+$",\n+ ignore=r"[\\(\\)\\-\\s\\+]",\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid international phone number."),\n+ ),\n+ RegexValidator(\n+ "isZipCode",\n+ r"^(\\d{5}|\\d{9})$",\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid zip code."),\n+ ),\n+ RegexValidator(\n+ "isURL",\n+ r"(%s)s?://[^\\s\\r\\n]+" % "|".join(protocols),\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid url."),\n+ ),\n+ RegexValidator(\n+ "isEmail",\n+ "^" + EMAIL_RE,\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid email address."),\n+ ),\n+ RegexValidator(\n+ "isMailto",\n+ "^mailto:" + EMAIL_RE,\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid email address."),\n+ ),\n+ RegexValidator(\n+ "isUnixLikeName",\n+ r"^[A-Za-z][\\w\\d\\-\\_]{0,7}$",\n+ title="",\n+ description="",\n+ errmsg=_("this name is not a valid identifier"),\n+ ),\n+]\n \n-__all__ = (\'baseValidators\', )\n+__all__ = ("baseValidators",)\ndiff --git a/Products/validation/validators/EmptyValidator.py b/Products/validation/validators/EmptyValidator.py\nindex e4257d8..948da9c 100644\n--- a/Products/validation/validators/EmptyValidator.py\n+++ b/Products/validation/validators/EmptyValidator.py\n@@ -11,47 +11,52 @@\n @implementer(IValidator)\n class EmptyValidator:\n \n- def __init__(self, name, title=\'\', description=\'\', showError=True):\n+ def __init__(self, name, title="", description="", showError=True):\n self.name = name\n self.title = title or name\n self.description = description\n self.showError = showError\n \n def __call__(self, value, *args, **kwargs):\n- isEmpty = kwargs.get(\'isEmpty\', False)\n- instance = kwargs.get(\'instance\', None)\n- field = kwargs.get(\'field\', None)\n+ isEmpty = kwargs.get("isEmpty", False)\n+ instance = kwargs.get("instance", None)\n+ field = kwargs.get("field", None)\n \n # XXX: This is a temporary fix. Need to be fixed right for AT 2.0\n # content_edit / BaseObject.processForm() calls\n # widget.process_form a second time!\n if instance and field:\n- widget = field.widget\n- request = getattr(instance, \'REQUEST\', None)\n+ widget = field.widget\n+ request = getattr(instance, "REQUEST", None)\n if request and request.form:\n- form = request.form\n- result = widget.process_form(instance, field, form,\n- empty_marker=_marker,\n- emptyReturnsMarker=True)\n+ form = request.form\n+ result = widget.process_form(\n+ instance, field, form, empty_marker=_marker, emptyReturnsMarker=True\n+ )\n if result is _marker or result is None:\n isEmpty = True\n \n if isEmpty:\n return True\n- elif value == \'\' or value is None:\n+ elif value == "" or value is None:\n return True\n else:\n- if getattr(self, \'showError\', False):\n- msg = _("Validation failed($name): \'$value\' is not empty.",\n- mapping = { \'name\' : safe_unicode(self.name), \'value\': safe_unicode(value)})\n+ if getattr(self, "showError", False):\n+ msg = _(\n+ "Validation failed($name): \'$value\' is not empty.",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n else:\n return False\n \n-validatorList = [\n- EmptyValidator(\'isEmpty\', title=\'\', description=\'\'),\n- EmptyValidator(\'isEmptyNoError\', title=\'\', description=\'\', showError=False),\n- ]\n \n-__all__ = (\'validatorList\', )\n+validatorList = [\n+ EmptyValidator("isEmpty", title="", description=""),\n+ EmptyValidator("isEmptyNoError", title="", description="", showError=False),\n+]\n \n+__all__ = ("validatorList",)\ndiff --git a/Products/validation/validators/ExpressionValidator.py b/Products/validation/validators/ExpressionValidator.py\nindex d75382c..16993e1 100644\n--- a/Products/validation/validators/ExpressionValidator.py\n+++ b/Products/validation/validators/ExpressionValidator.py\n@@ -32,7 +32,7 @@\n \n @implementer(IValidator)\n class ExpressionValidator:\n- """ Validator for TALES Expressions\n+ """Validator for TALES Expressions\n \n Basically, if the expression compiles it\'s a valid expression,\n otherwise it\'s invalid and you get a message saying that the\n@@ -58,42 +58,44 @@ class ExpressionValidator:\n \n """\n \n- name = \'talesexpressionvalidator\'\n+ name = "talesexpressionvalidator"\n \n- def __init__(self,expression=None,errormsg=None):\n- self.expression=expression\n- self.errormsg=errormsg\n+ def __init__(self, expression=None, errormsg=None):\n+ self.expression = expression\n+ self.errormsg = errormsg\n if expression:\n- self.compiledExpression=getEngine().compile(expression)\n+ self.compiledExpression = getEngine().compile(expression)\n \n def __call__(self, value, instance, *args, **kwargs):\n- kw={\n- \'here\':instance,\n- \'object\':instance,\n- \'instance\':instance,\n- \'value\':value,\n- \'args\':args,\n- \'kwargs\':kwargs,\n- }\n-\n- context=getEngine().getContext(kw)\n- res=self.compiledExpression(context)\n+ kw = {\n+ "here": instance,\n+ "object": instance,\n+ "instance": instance,\n+ "value": value,\n+ "args": args,\n+ "kwargs": kwargs,\n+ }\n+\n+ context = getEngine().getContext(kw)\n+ res = self.compiledExpression(context)\n \n if res:\n return True\n else:\n if self.errormsg and type(self.errormsg) == Message:\n- #hack to support including values in i18n message, too. hopefully this works out\n- #potentially it could unintentionally overwrite already present values\n+ # hack to support including values in i18n message, too. hopefully this works out\n+ # potentially it could unintentionally overwrite already present values\n self.errormsg.mapping = kw\n return recursiveTranslate(self.errormsg, **kwargs)\n elif self.errormsg:\n # support strings as errormsg for backward compatibility\n return self.errormsg % kw\n else:\n- msg = _(\'validation failed, expr was:$expr\',\n- mapping={\'expr\': safe_unicode(self.expression)})\n+ msg = _(\n+ "validation failed, expr was:$expr",\n+ mapping={"expr": safe_unicode(self.expression)},\n+ )\n return recursiveTranslate(msg, **kwargs)\n \n \n-#validation.register(TALESExpressionValidator())\n+# validation.register(TALESExpressionValidator())\ndiff --git a/Products/validation/validators/IdValidator.py b/Products/validation/validators/IdValidator.py\nindex 7d2dde7..8301ed2 100644\n--- a/Products/validation/validators/IdValidator.py\n+++ b/Products/validation/validators/IdValidator.py\n@@ -13,17 +13,17 @@\n \n def fallback_check_id(instance, id, **kwargs):\n # space test\n- if \' \' in id:\n- msg = _(\'Spaces are not allowed in ids\')\n+ if " " in id:\n+ msg = _("Spaces are not allowed in ids")\n return recursiveTranslate(msg, **kwargs)\n \n # in parent test\n parent = aq_parent(aq_inner(instance))\n # If the id is given to a different object already\n- if (id in parent.objectIds() and\n- getattr(aq_base(parent), id) is not aq_base(instance)):\n- msg = _(\'Id $id is already in use\',\n- mapping = {\'id\': safe_unicode(id)})\n+ if id in parent.objectIds() and getattr(aq_base(parent), id) is not aq_base(\n+ instance\n+ ):\n+ msg = _("Id $id is already in use", mapping={"id": safe_unicode(id)})\n return recursiveTranslate(msg, **kwargs)\n \n # object manager test\n@@ -39,7 +39,7 @@ def fallback_check_id(instance, id, **kwargs):\n @implementer(IValidator)\n class IdValidator:\n \n- def __init__( self, name, title=\'\', description=\'\'):\n+ def __init__(self, name, title="", description=""):\n self.name = name\n self.title = title or name\n self.description = description\n@@ -49,10 +49,10 @@ def __call__(self, id, instance, *args, **kwargs):\n # for example a Python skin script or a method,\n # like Products/CMFPlone/skins/plone_scripts/check_id.py\n # until Plone 5.1.\n- check_id = aq_get(instance, \'check_id\', None, 1)\n+ check_id = aq_get(instance, "check_id", None, 1)\n if check_id is not None:\n # instance is passed implicitly: it is \'self\'\n- result = check_id(id, required=kwargs.get(\'required\'))\n+ result = check_id(id, required=kwargs.get("required"))\n else:\n try:\n # try to use the check_id script of CMFPlone\n@@ -66,12 +66,12 @@ def __call__(self, id, instance, *args, **kwargs):\n result = check_id(instance, id, **kwargs)\n else:\n # Only the \'required\' keyword is accepted.\n- result = check_id(instance, id, required=kwargs.get(\'required\'))\n+ result = check_id(instance, id, required=kwargs.get("required"))\n return result or 1\n \n \n validatorList = [\n- IdValidator(\'isValidId\', title=\'\', description=\'\'),\n- ]\n+ IdValidator("isValidId", title="", description=""),\n+]\n \n-__all__ = (\'validatorList\', )\n+__all__ = ("validatorList",)\ndiff --git a/Products/validation/validators/RangeValidator.py b/Products/validation/validators/RangeValidator.py\nindex 33e644f..ba1fed2 100644\n--- a/Products/validation/validators/RangeValidator.py\n+++ b/Products/validation/validators/RangeValidator.py\n@@ -8,7 +8,7 @@\n @implementer(IValidator)\n class RangeValidator:\n \n- def __init__(self, name, minval=0.0, maxval=0.0, title=\'\', description=\'\'):\n+ def __init__(self, name, minval=0.0, maxval=0.0, title="", description=""):\n self.name = name\n self.minval = minval\n self.maxval = maxval\n@@ -16,31 +16,35 @@ def __init__(self, name, minval=0.0, maxval=0.0, title=\'\', description=\'\'):\n self.description = description\n \n def __call__(self, value, *args, **kwargs):\n- if len(args)>=1:\n- minval=args[0]\n+ if len(args) >= 1:\n+ minval = args[0]\n else:\n- minval=self.minval\n+ minval = self.minval\n \n- if len(args)>=2:\n- maxval=args[1]\n+ if len(args) >= 2:\n+ maxval = args[1]\n else:\n- maxval=self.maxval\n+ maxval = self.maxval\n \n- assert(minval <= maxval)\n+ assert minval <= maxval\n try:\n nval = float(value)\n except ValueError:\n- msg = _("Validation failed($name): could not convert \'$value\' to number",\n- mapping = { \'name\' : safe_unicode(self.name), \'value\': safe_unicode(value)})\n+ msg = _(\n+ "Validation failed($name): could not convert \'$value\' to number",\n+ mapping={"name": safe_unicode(self.name), "value": safe_unicode(value)},\n+ )\n return recursiveTranslate(msg, **kwargs)\n if minval <= nval < maxval:\n return 1\n \n- msg = _("Validation failed($name): \'$value\' out of range($min, $max)",\n- mapping = {\n- \'name\' : safe_unicode(self.name),\n- \'value\': safe_unicode(value),\n- \'min\' : safe_unicode(minval),\n- \'max\' : safe_unicode(maxval),\n- })\n+ msg = _(\n+ "Validation failed($name): \'$value\' out of range($min, $max)",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ "min": safe_unicode(minval),\n+ "max": safe_unicode(maxval),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\ndiff --git a/Products/validation/validators/RegexValidator.py b/Products/validation/validators/RegexValidator.py\nindex 36e6493..beec9ee 100644\n--- a/Products/validation/validators/RegexValidator.py\n+++ b/Products/validation/validators/RegexValidator.py\n@@ -9,7 +9,7 @@\n \n def ignoreRE(value, expression):\n ignore = re.compile(expression)\n- return ignore.sub(\'\', value)\n+ return ignore.sub("", value)\n \n \n @implementer(IValidator)\n@@ -17,11 +17,11 @@ class RegexValidator:\n \n def __init__(self, name, *args, **kw):\n self.name = name\n- self.title = kw.get(\'title\', name)\n- self.description = kw.get(\'description\', \'\')\n- self.errmsg = kw.get(\'errmsg\', \'fails tests of %s\' % name)\n+ self.title = kw.get("title", name)\n+ self.description = kw.get("description", "")\n+ self.errmsg = kw.get("errmsg", "fails tests of %s" % name)\n self.regex_strings = args\n- self.ignore = kw.get(\'ignore\', None)\n+ self.ignore = kw.get("ignore", None)\n self.regex = []\n self.compileRegex()\n \n@@ -34,7 +34,7 @@ def __getstate__(self):\n I\'m using the getstate/setstate hooks to set self.regex to []\n """\n d = self.__dict__.copy()\n- d[\'regex\'] = []\n+ d["regex"] = []\n return d\n \n def __setstate__(self, dict):\n@@ -43,30 +43,33 @@ def __setstate__(self, dict):\n \n def __call__(self, value, *args, **kwargs):\n if not isinstance(value, str):\n- msg = _("Validation failed($name): $value of type $type, expected \'string\'",\n- mapping = {\n- \'name\' : safe_unicode(self.name),\n- \'value\': safe_unicode(value),\n- \'type\' : safe_unicode(type(value))\n- })\n+ msg = _(\n+ "Validation failed($name): $value of type $type, expected \'string\'",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ "type": safe_unicode(type(value)),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n \n- ignore = kwargs.get(\'ignore\', None)\n+ ignore = kwargs.get("ignore", None)\n if ignore:\n value = ignoreRE(value, ignore)\n elif self.ignore:\n value = ignoreRE(value, self.ignore)\n \n-\n for r in self.regex:\n m = r.match(value)\n if not m:\n- msg = _("Validation failed($name): \'$value\' $errmsg",\n- mapping={\n- \'name\' : safe_unicode(self.name),\n- \'value\': safe_unicode(value),\n- \'errmsg\' : safe_unicode(self.errmsg)\n- })\n+ msg = _(\n+ "Validation failed($name): \'$value\' $errmsg",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ "errmsg": safe_unicode(self.errmsg),\n+ },\n+ )\n \n return recursiveTranslate(msg, **kwargs)\n return 1\ndiff --git a/Products/validation/validators/SupplValidators.py b/Products/validation/validators/SupplValidators.py\nindex a08218d..c097940 100644\n--- a/Products/validation/validators/SupplValidators.py\n+++ b/Products/validation/validators/SupplValidators.py\n@@ -25,22 +25,22 @@ class MaxSizeValidator:\n or a given maxsize at validator initialization.\n """\n \n- def __init__(self, name, title=\'\', description=\'\', maxsize=0):\n+ def __init__(self, name, title="", description="", maxsize=0):\n self.name = name\n self.title = title or name\n self.description = description\n- self.maxsize= maxsize\n+ self.maxsize = maxsize\n \n def __call__(self, value, *args, **kwargs):\n- instance = kwargs.get(\'instance\', None)\n- field = kwargs.get(\'field\', None)\n+ instance = kwargs.get("instance", None)\n+ field = kwargs.get("field", None)\n \n # get max size\n- if kwargs.has_key(\'maxsize\'):\n- maxsize = kwargs.get(\'maxsize\')\n- elif hasattr(aq_base(instance), \'getMaxSizeFor\'):\n+ if kwargs.has_key("maxsize"):\n+ maxsize = kwargs.get("maxsize")\n+ elif hasattr(aq_base(instance), "getMaxSizeFor"):\n maxsize = instance.getMaxSizeFor(field.getName())\n- elif hasattr(field, \'maxsize\'):\n+ elif hasattr(field, "maxsize"):\n maxsize = field.maxsize\n else:\n # set to given default value (default defaults to 0)\n@@ -50,9 +50,12 @@ def __call__(self, value, *args, **kwargs):\n return True\n \n # calculate size\n- elif (isinstance(value, FileUpload) or isinstance(value, file) or\n- hasattr(aq_base(value), \'tell\')):\n- value.seek(0, 2) # eof\n+ elif (\n+ isinstance(value, FileUpload)\n+ or isinstance(value, file)\n+ or hasattr(aq_base(value), "tell")\n+ ):\n+ value.seek(0, 2) # eof\n size = value.tell()\n value.seek(0)\n else:\n@@ -61,15 +64,17 @@ def __call__(self, value, *args, **kwargs):\n except TypeError:\n size = 0\n size = float(size)\n- sizeMB = (size / (1024 * 1024))\n+ sizeMB = size / (1024 * 1024)\n \n if sizeMB > maxsize:\n- msg = _("Validation failed($name: Uploaded data is too large: ${size}MB (max ${max}MB)",\n- mapping = {\n- \'name\' : safe_unicode(self.name),\n- \'size\' : safe_unicode("%.3f" % sizeMB),\n- \'max\' : safe_unicode("%.3f" % maxsize)\n- })\n+ msg = _(\n+ "Validation failed($name: Uploaded data is too large: ${size}MB (max ${max}MB)",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "size": safe_unicode("%.3f" % sizeMB),\n+ "max": safe_unicode("%.3f" % maxsize),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n else:\n return True\n@@ -78,29 +83,36 @@ def __call__(self, value, *args, **kwargs):\n @implementer(IValidator)\n class DateValidator:\n \n- def __init__(self, name, title=\'\', description=\'\'):\n+ def __init__(self, name, title="", description=""):\n self.name = name\n self.title = title or name\n self.description = description\n \n def __call__(self, value, *args, **kwargs):\n if not value:\n- msg = _("Validation failed($name): value is empty ($value).",\n- mapping = {\'name\': self.name, \'value\': repr(value)})\n+ msg = _(\n+ "Validation failed($name): value is empty ($value).",\n+ mapping={"name": self.name, "value": repr(value)},\n+ )\n return recursiveTranslate(msg, **kwargs)\n if not isinstance(value, DateTime):\n try:\n value = DateTime(value)\n except:\n- msg = _("Validation failed($name): could not convert $value to a date.",\n- mapping = {\'name\': safe_unicode(self.name), \'value\': safe_unicode(value)})\n+ msg = _(\n+ "Validation failed($name): could not convert $value to a date.",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n return True\n \n \n validatorList = [\n- MaxSizeValidator(\'isMaxSize\', title=\'\', description=\'\'),\n- DateValidator(\'isValidDate\', title=\'\', description=\'\'),\n- ]\n+ MaxSizeValidator("isMaxSize", title="", description=""),\n+ DateValidator("isValidDate", title="", description=""),\n+]\n \n-__all__ = (\'validatorList\', )\n+__all__ = ("validatorList",)\ndiff --git a/setup.py b/setup.py\nindex 76a1a6a..8ba08fb 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -2,14 +2,13 @@\n from setuptools import setup\n \n \n-version = \'2.1.4.dev0\'\n+version = "2.1.4.dev0"\n \n setup(\n- name=\'Products.validation\',\n+ name="Products.validation",\n version=version,\n description="Data validation package for Zope",\n- long_description=(open("README.rst").read() + "\\n" +\n- open("CHANGES.rst").read()),\n+ long_description=(open("README.rst").read() + "\\n" + open("CHANGES.rst").read()),\n classifiers=[\n "Development Status :: 6 - Mature",\n "Framework :: Zope2",\n@@ -27,23 +26,23 @@\n "Programming Language :: Python :: 3.7",\n "Programming Language :: Python :: 3.8",\n ],\n- keywords=\'Zope validation regex email\',\n- author=\'Benjamin Saller\',\n- author_email=\'plone-developers@lists.sourceforge.net\',\n- url=\'https://pypi.org/project/Products.validation\',\n- license=\'GPL\',\n+ keywords="Zope validation regex email",\n+ author="Benjamin Saller",\n+ author_email="plone-developers@lists.sourceforge.net",\n+ url="https://pypi.org/project/Products.validation",\n+ license="GPL",\n packages=find_packages(),\n- namespace_packages=[\'Products\'],\n+ namespace_packages=["Products"],\n include_package_data=True,\n zip_safe=False,\n extras_require={},\n install_requires=[\n- \'setuptools\',\n- \'zope.i18n\',\n- \'zope.i18nmessageid\',\n- \'zope.interface\',\n- \'Acquisition\',\n- \'DateTime\',\n- \'Zope2\',\n+ "setuptools",\n+ "zope.i18n",\n+ "zope.i18nmessageid",\n+ "zope.interface",\n+ "Acquisition",\n+ "DateTime",\n+ "Zope2",\n ],\n )\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T11:45:40+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/edb7acefdb6bceac96b6003dc3e9b47f66302d13 - -Another pyupgrade. - -Files changed: -M Products/validation/chain.py - -b'diff --git a/Products/validation/chain.py b/Products/validation/chain.py\nindex c11d76a..4772caa 100644\n--- a/Products/validation/chain.py\n+++ b/Products/validation/chain.py\n@@ -38,7 +38,7 @@ def __repr__(self):\n val = []\n for validator, mode in self:\n name = validator.name\n- val.append("(\'{}\', {})".format(name, map.get(mode)))\n+ val.append(f"(\'{name}\', {map.get(mode)})")\n return "(%s)" % ", ".join(val)\n \n def __len__(self):\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:24+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/7c8b28a37fe306e39176102bcbdc1902ac8b8eca - -doesnt -> does not - -Files changed: -M Products/validation/validators/ExpressionValidator.py - -b'diff --git a/Products/validation/validators/ExpressionValidator.py b/Products/validation/validators/ExpressionValidator.py\nindex 16993e1..f639a0d 100644\n--- a/Products/validation/validators/ExpressionValidator.py\n+++ b/Products/validation/validators/ExpressionValidator.py\n@@ -39,7 +39,7 @@ class ExpressionValidator:\n expression has errors.\n \n \n- >>> val=ExpressionValidator(\'python: int(value) == 5\')\n+ >>> val=ExpressionValidator("python: int(value) == 5")\n >>> class C:i=1\n >>> c=C()\n >>> val(5,c) is True\n@@ -51,9 +51,9 @@ class ExpressionValidator:\n \n It is also possible to specify the error string\n \n- >>> val=ExpressionValidator(\'python: int(value) == 5\', \'value doesnt match %(value)s\')\n+ >>> val=ExpressionValidator("python: int(value) == 5", "value does not match %(value)s")\n >>> val(4,c)\n- \'value doesnt match 4\'\n+ \'value does not match 4\'\n \n \n """\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:24+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/610cba65857d20044de1f24a80d1fe1c0516547c - -ispell - -Files changed: -M CHANGES.rst -M docs/ChangeLog - -b"diff --git a/CHANGES.rst b/CHANGES.rst\nindex 3b90426..92a4787 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -89,7 +89,7 @@ Bug fixes:\n [hannosch]\n \n - Purge old zope2 Interface interfaces for Zope 2.12 compatibility.\n- Consider branching before this checkin if release required before Plone 4.\n+ Consider branching before this commit if release required before Plone 4.\n [elro]\n \n - Cleaned up lots of old BBB code.\n@@ -161,7 +161,7 @@ Bug fixes:\n Archetypes 1.4.2 (used for Plone 2.5.2) of PortalTransforms is then the 1.5\n final.\n We dont need increasing of release numbers because of Plone 3.0,\n- Archetypes 1.5, ... if theres no change in the dependend product, like\n+ Archetypes 1.5, ... if there's no change in the dependent product, like\n this one.\n [jensens]\n \ndiff --git a/docs/ChangeLog b/docs/ChangeLog\nindex 2eb58a3..feee611 100644\n--- a/docs/ChangeLog\n+++ b/docs/ChangeLog\n@@ -38,7 +38,7 @@ DONT USE ChangeLog - USE history.txt instead!\n and description to validators.\n \n * service.py (Service.items): Improve the service a bit, to be\n- useable with the other registries.\n+ usable with the other registries.\n \n 2003-08-04 Sylvain Th\xe9nault \n \n" - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:25+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/a452d4cd63074bc9b040df9fe33f6f42783c003b - -flake8: ignore unused imports in a few files. - -Files changed: -M .flake8 -M .meta.toml - -b'diff --git a/.flake8 b/.flake8\nindex 38918f4..92fca63 100644\n--- a/.flake8\n+++ b/.flake8\n@@ -12,6 +12,10 @@ ignore =\n E203,\n # black takes care of spaces after commas\n E231,\n+per-file-ignores =\n+ Products/validation/__init__.py:F401\n+ Products/validation/interfaces/__init__.py:F401\n+ Products/validation/validators/validator.py:F401\n \n ##\n # Add extra configuration options in .meta.toml:\ndiff --git a/.meta.toml b/.meta.toml\nindex 83eecdf..445d528 100644\n--- a/.meta.toml\n+++ b/.meta.toml\n@@ -4,3 +4,11 @@\n [meta]\n template = "default"\n commit-id = "4b86f8f1"\n+\n+[flake8]\n+extra_lines = """\n+per-file-ignores =\n+ Products/validation/__init__.py:F401\n+ Products/validation/interfaces/__init__.py:F401\n+ Products/validation/validators/validator.py:F401\n+"""\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:25+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/1f59b677cf284ad89cb9e590ded8f186b1ad2b57 - -Explain the imports in Products/validation/validators/__init__.py - -Change the order, isort was complaining about most of the imports not being at the top. - -Files changed: -M Products/validation/validators/__init__.py - -b"diff --git a/Products/validation/validators/__init__.py b/Products/validation/validators/__init__.py\nindex c688dae..181fe91 100644\n--- a/Products/validation/validators/__init__.py\n+++ b/Products/validation/validators/__init__.py\n@@ -1,29 +1,32 @@\n-from Products.validation.validators.ExpressionValidator import ExpressionValidator\n-from Products.validation.validators.RangeValidator import RangeValidator\n-from Products.validation.validators.RegexValidator import RegexValidator\n-\n-\n-validators = []\n-\n+# This module gathers a list of validators, and registers them to the service.\n+#\n+# Three of these are backwards compatibility imports, marked with 'noqa F401'\n+# for flake8. Of these, the RangeValidator and RegexValidator are included in\n+# baseValidators, so that is fine.\n+# ExpressionValidator is not included. This is because you need to create an\n+# instance of a validator class and register this. The ExpressionValidator\n+# class is fine as base for your custom validators, but no one thought of\n+# a default instance to add here.\n from Products.validation.validators.BaseValidators import baseValidators\n+from Products.validation.validators.EmptyValidator import (\n+ validatorList as emptyValidators,\n+)\n+from Products.validation.validators.ExpressionValidator import ( # noqa F401\n+ ExpressionValidator,\n+)\n+from Products.validation.validators.IdValidator import validatorList as idValidators\n+from Products.validation.validators.RangeValidator import RangeValidator # noqa F401\n+from Products.validation.validators.RegexValidator import RegexValidator # noqa F401\n+from Products.validation.validators.SupplValidators import (\n+ validatorList as supplValidators,\n+)\n \n \n+validators = []\n validators.extend(baseValidators)\n-\n-from Products.validation.validators.EmptyValidator import validatorList\n-\n-\n-validators.extend(validatorList)\n-\n-from Products.validation.validators.SupplValidators import validatorList\n-\n-\n-validators.extend(validatorList)\n-\n-from Products.validation.validators.IdValidator import validatorList\n-\n-\n-validators.extend(validatorList)\n+validators.extend(emptyValidators)\n+validators.extend(supplValidators)\n+validators.extend(idValidators)\n \n \n def initialize(service):\n" - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:25+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/c0a63bbf7424963fd4afdd04ce67fb0a082a66a1 - -Remove really unused imports. - -Files changed: -M Products/validation/interfaces/IValidationService.py -M Products/validation/service.py -M Products/validation/tests/test_validation.py - -b'diff --git a/Products/validation/interfaces/IValidationService.py b/Products/validation/interfaces/IValidationService.py\nindex a2fc596..8c3aa64 100644\n--- a/Products/validation/interfaces/IValidationService.py\n+++ b/Products/validation/interfaces/IValidationService.py\n@@ -1,4 +1,3 @@\n-from zope.interface import Attribute\n from zope.interface import Interface\n \n \ndiff --git a/Products/validation/service.py b/Products/validation/service.py\nindex 6aaa368..4b46f6a 100644\n--- a/Products/validation/service.py\n+++ b/Products/validation/service.py\n@@ -1,4 +1,3 @@\n-from Products.validation.exceptions import AlreadyRegisteredValidatorError\n from Products.validation.exceptions import FalseValidatorError\n from Products.validation.exceptions import UnknowValidatorError\n from Products.validation.interfaces.IValidationService import IValidationService\ndiff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex 060f862..1ecd766 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -134,8 +134,6 @@ def test_isUnixLikeName(self):\n self.assertNotEqual(v("aaaaaaaab"), 1) # too long\n \n def test_isValidId_basic(self):\n- from Products.validation.validators import IdValidator\n-\n v = validation.validatorFor("isValidId")\n obj = Dummy("foo")\n \n@@ -145,10 +143,10 @@ def test_isValidId_basic(self):\n self.assertEqual(v("a b", obj), "bad id")\n \n def test_isValidId_plone(self):\n- from Products.validation.validators import IdValidator\n-\n try:\n- from Products.CMFPlone.utils import check_id\n+ # We do not use this import directly, but if it fails,\n+ # this test is useless.\n+ from Products.CMFPlone.utils import check_id # noqa F401\n except ImportError:\n return\n \n@@ -179,8 +177,6 @@ def test_isValidId_plone(self):\n self.assertEqual(v("layout", obj), "${name} is reserved.")\n \n def test_isValidId_fallback(self):\n- from Products.validation.validators import IdValidator\n-\n # We can only check this if utils.check_id gives an ImportError.\n try:\n from Products.CMFPlone.utils import check_id as plone_check_id\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:25+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/faf4aa3856899ac274f1eb2c46d01d1dcdf16615 - -More flake8 fixes. - -Files changed: -M Products/validation/chain.py -M Products/validation/i18n.py -M Products/validation/service.py -M Products/validation/validators/ExpressionValidator.py -M Products/validation/validators/SupplValidators.py - -b'diff --git a/Products/validation/chain.py b/Products/validation/chain.py\nindex 4772caa..7267683 100644\n--- a/Products/validation/chain.py\n+++ b/Products/validation/chain.py\n@@ -120,7 +120,7 @@ def __call__(self, value, *args, **kwargs):\n for validator, mode in self:\n name = validator.name\n result = validator(value, *args, **kwargs)\n- if result == True:\n+ if result is True:\n if mode == V_SUFFICIENT:\n return True # validation was successful\n elif mode == V_REQUIRED:\n@@ -145,7 +145,7 @@ def __call__(self, value, *args, **kwargs):\n if failed:\n return "\\n".join(\n [\n- #\'%s: %s\' % (name, res)\n+ # \'%s: %s\' % (name, res)\n "%s" % res\n for name, res in results.items()\n ]\ndiff --git a/Products/validation/i18n.py b/Products/validation/i18n.py\nindex 6133ea7..8e2f6a8 100644\n--- a/Products/validation/i18n.py\n+++ b/Products/validation/i18n.py\n@@ -20,7 +20,7 @@ def recursiveTranslate(message, **kwargs):\n map = message.mapping\n if map:\n for key in map.keys():\n- if type(map[key]) == Message:\n+ if isinstance(map[key], Message):\n map[key] = translate(map[key], context=request)\n \n return translate(message, context=request)\ndiff --git a/Products/validation/service.py b/Products/validation/service.py\nindex 4b46f6a..6243711 100644\n--- a/Products/validation/service.py\n+++ b/Products/validation/service.py\n@@ -28,13 +28,13 @@ def validatorFor(self, name_or_validator):\n else:\n raise FalseValidatorError(name_or_validator)\n \n- def register(self, validator): # XXX\n+ def register(self, validator):\n if not IValidator.providedBy(validator):\n raise FalseValidatorError(validator)\n name = validator.name\n # The following code prevents refreshing\n- ##if self._validator.has_key(name):\n- ## raise AlreadyRegisteredValidatorError, name\n+ # if self._validator.has_key(name):\n+ # raise AlreadyRegisteredValidatorError, name\n self._validator[name] = validator\n \n def items(self):\ndiff --git a/Products/validation/validators/ExpressionValidator.py b/Products/validation/validators/ExpressionValidator.py\nindex f639a0d..59fd52f 100644\n--- a/Products/validation/validators/ExpressionValidator.py\n+++ b/Products/validation/validators/ExpressionValidator.py\n@@ -82,7 +82,7 @@ def __call__(self, value, instance, *args, **kwargs):\n if res:\n return True\n else:\n- if self.errormsg and type(self.errormsg) == Message:\n+ if self.errormsg and isinstance(self.errormsg, Message):\n # hack to support including values in i18n message, too. hopefully this works out\n # potentially it could unintentionally overwrite already present values\n self.errormsg.mapping = kw\ndiff --git a/Products/validation/validators/SupplValidators.py b/Products/validation/validators/SupplValidators.py\nindex c097940..8e56a1a 100644\n--- a/Products/validation/validators/SupplValidators.py\n+++ b/Products/validation/validators/SupplValidators.py\n@@ -98,7 +98,7 @@ def __call__(self, value, *args, **kwargs):\n if not isinstance(value, DateTime):\n try:\n value = DateTime(value)\n- except:\n+ except Exception:\n msg = _(\n "Validation failed($name): could not convert $value to a date.",\n mapping={\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:25+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/e1442f652fcb3ce103a852e55d091cb9463adfba - -Fix left-over Python 2 constructs that do not work in Python 3. - -Files changed: -M Products/validation/chain.py -M Products/validation/validators/SupplValidators.py - -b'diff --git a/Products/validation/chain.py b/Products/validation/chain.py\nindex 7267683..9ba9e98 100644\n--- a/Products/validation/chain.py\n+++ b/Products/validation/chain.py\n@@ -49,12 +49,26 @@ def __iter__(self):\n """Python 2.3 for i in x support"""\n return iter(zip(self._chain, self._v_mode))\n \n- def __cmp__(self, key):\n- if isinstance(key, ValidationChain):\n- str = repr(key)\n+ def __eq__(self, other):\n+ if isinstance(other, ValidationChain):\n+ value = repr(other)\n else:\n- str = key\n- return cmp(repr(self), str)\n+ value = other\n+ return repr(self) == value\n+\n+ def __lt__(self, other):\n+ if isinstance(other, ValidationChain):\n+ value = repr(other)\n+ else:\n+ value = other\n+ return repr(self) < value\n+\n+ def __gt__(self, other):\n+ if isinstance(other, ValidationChain):\n+ value = repr(other)\n+ else:\n+ value = other\n+ return repr(self) > value\n \n def __getitem__(self, idx):\n """self[idx] support and Python 2.1 for i in x support"""\n@@ -129,12 +143,12 @@ def __call__(self, value, *args, **kwargs):\n raise ValidatorError(f"Unknown mode {mode}")\n else:\n if mode == V_SUFFICIENT:\n- if isinstance(result, basestring):\n+ if isinstance(result, (str, bytes)):\n # don\'t log if validator doesn\'t return an error msg\n results[name] = result\n continue # no fatal error, go on\n elif mode == V_REQUIRED:\n- if isinstance(result, basestring):\n+ if isinstance(result, (str, bytes)):\n # don\'t log if validator doesn\'t return an error msg\n results[name] = result\n failed = True\ndiff --git a/Products/validation/validators/SupplValidators.py b/Products/validation/validators/SupplValidators.py\nindex 8e56a1a..a68f4fe 100644\n--- a/Products/validation/validators/SupplValidators.py\n+++ b/Products/validation/validators/SupplValidators.py\n@@ -1,5 +1,6 @@\n from Acquisition import aq_base\n from DateTime import DateTime\n+from io import TextIOWrapper\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n@@ -52,7 +53,7 @@ def __call__(self, value, *args, **kwargs):\n # calculate size\n elif (\n isinstance(value, FileUpload)\n- or isinstance(value, file)\n+ or isinstance(value, TextIOWrapper)\n or hasattr(aq_base(value), "tell")\n ):\n value.seek(0, 2) # eof\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:26+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/68ca1dbb01f8a65c69fd5001d77048cce957d261 - -Drop support for Plone 5.2 and for Python 3.7 and lower. - -Only Plone 6.0 and 6.1 are supported now. -Note that in Plone 6.1, this package is no longer tested together with the core. -It has been moved to the ecosystem versions. - -Files changed: -A news/60.breaking.1 -M setup.py - -b'diff --git a/news/60.breaking.1 b/news/60.breaking.1\nnew file mode 100644\nindex 0000000..1b0fd63\n--- /dev/null\n+++ b/news/60.breaking.1\n@@ -0,0 +1,5 @@\n+Drop support for Plone 5.2 and for Python 3.7 and lower.\n+Only Plone 6.0 and 6.1 are supported now.\n+Note that in Plone 6.1, this package is no longer tested together with the core.\n+It has been moved to the ecosystem versions.\n+[maurits]\ndiff --git a/setup.py b/setup.py\nindex 8ba08fb..d1389eb 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -2,7 +2,7 @@\n from setuptools import setup\n \n \n-version = "2.1.4.dev0"\n+version = "3.0.0.dev0"\n \n setup(\n name="Products.validation",\n@@ -11,31 +11,31 @@\n long_description=(open("README.rst").read() + "\\n" + open("CHANGES.rst").read()),\n classifiers=[\n "Development Status :: 6 - Mature",\n- "Framework :: Zope2",\n- "Framework :: Zope :: 4",\n+ "Framework :: Zope",\n "Framework :: Zope :: 5",\n "Operating System :: OS Independent",\n "Framework :: Plone",\n- "Framework :: Plone :: 5.1",\n- "Framework :: Plone :: 5.2",\n "Framework :: Plone :: 6.0",\n+ "Framework :: Plone :: 6.1",\n "License :: OSI Approved :: GNU General Public License (GPL)",\n "Programming Language :: Python",\n- "Programming Language :: Python :: 2.7",\n- "Programming Language :: Python :: 3.6",\n- "Programming Language :: Python :: 3.7",\n "Programming Language :: Python :: 3.8",\n+ "Programming Language :: Python :: 3.9",\n+ "Programming Language :: Python :: 3.10",\n+ "Programming Language :: Python :: 3.11",\n+ "Programming Language :: Python :: 3.12",\n ],\n keywords="Zope validation regex email",\n author="Benjamin Saller",\n author_email="plone-developers@lists.sourceforge.net",\n- url="https://pypi.org/project/Products.validation",\n+ url="https://github.com/plone/Products.validation",\n license="GPL",\n packages=find_packages(),\n namespace_packages=["Products"],\n include_package_data=True,\n zip_safe=False,\n extras_require={},\n+ python_requires=">=3.8",\n install_requires=[\n "setuptools",\n "zope.i18n",\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:26+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/5b000d0045433c9678b67f94a66c295af5033122 - -Depend on Zope. Most other dependencies then get pulled in automatically. - -We could list them apart, but we already need Zope because we use OFS from that package. - -Files changed: -M setup.py - -b'diff --git a/setup.py b/setup.py\nindex d1389eb..e1bfc0e 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -38,11 +38,6 @@\n python_requires=">=3.8",\n install_requires=[\n "setuptools",\n- "zope.i18n",\n- "zope.i18nmessageid",\n- "zope.interface",\n- "Acquisition",\n- "DateTime",\n- "Zope2",\n+ "Zope",\n ],\n )\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:26+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/cc394aadc3be5f050ad1497b303d0e869af38a28 - -Removed `Products/validation/validators/validator.py`. - -This had backwards compatibility imports in a way that did not work on Python 3. -So apparently no one needed this so far. - -Files changed: -A news/60.breaking.2 -D Products/validation/validators/validator.py - -b'diff --git a/Products/validation/validators/validator.py b/Products/validation/validators/validator.py\ndeleted file mode 100644\nindex 6dcaee3..0000000\n--- a/Products/validation/validators/validator.py\n+++ /dev/null\n@@ -1,3 +0,0 @@\n-# backward compatibility\n-from RangeValidator import RangeValidator\n-from RegexValidator import RegexValidator\ndiff --git a/news/60.breaking.2 b/news/60.breaking.2\nnew file mode 100644\nindex 0000000..e5c2cc5\n--- /dev/null\n+++ b/news/60.breaking.2\n@@ -0,0 +1,4 @@\n+Removed `Products/validation/validators/validator.py`.\n+This had backwards compatibility imports in a way that did not work on Python 3.\n+So apparently no one needed this so far.\n+[maurits]\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T13:03:26+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/af24dc33da88773395cc4e64088ee8044ab0da63 - -Dependencies: ignore Products.CMFPlone. - -We try an import of of its `utils.check_id`, but we always catch an ImportError, so the dependency is conditional. - -Files changed: -M .meta.toml -M pyproject.toml - -b'diff --git a/.meta.toml b/.meta.toml\nindex 445d528..20c2d9e 100644\n--- a/.meta.toml\n+++ b/.meta.toml\n@@ -12,3 +12,6 @@ per-file-ignores =\n Products/validation/interfaces/__init__.py:F401\n Products/validation/validators/validator.py:F401\n """\n+\n+[pyproject]\n+dependencies_ignores = "[\'Products.CMFPlone\']"\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 653b7e2..dc211d3 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -119,6 +119,7 @@ Zope = [\n \'Products.CMFCore\', \'Products.CMFDynamicViewFTI\',\n ]\n python-dateutil = [\'dateutil\']\n+ignore-packages = [\'Products.CMFPlone\']\n \n ##\n # Add extra configuration options in .meta.toml:\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T15:06:08+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/326ad07553b101e5381944d43d7fbf507339f335 - -Remove check in tests for Plone 5.1. - -Files changed: -M Products/validation/tests/test_validation.py - -b'diff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex 1ecd766..219120f 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -166,14 +166,6 @@ def test_isValidId_plone(self):\n # Plone seems to allow spaces.\n self.assertEqual(v("a b", obj), 1)\n # Some ids are forbidden in Plone. We get an i18n message back.\n- # Problem: on Plone 5.1, utils.check_id simply looks for a\n- # check_id script/attribute on the context. This will fail.\n- # So only test this in Plone 5.2+, not on 5.1.\n- import pkg_resources\n-\n- version = pkg_resources.get_distribution("Products.CMFPlone").version\n- if version.startswith("5.1"):\n- return\n self.assertEqual(v("layout", obj), "${name} is reserved.")\n \n def test_isValidId_fallback(self):\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T15:06:48+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/de7336c224b7831f09208ff7442bcbd2bafb4ceb - -No, we don't want to fall back to pkgutil at any time. - -Files changed: -M Products/__init__.py - -b'diff --git a/Products/__init__.py b/Products/__init__.py\nindex 05f0beb..5284146 100644\n--- a/Products/__init__.py\n+++ b/Products/__init__.py\n@@ -1,7 +1 @@\n-# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages\n-try:\n- __import__("pkg_resources").declare_namespace(__name__)\n-except ImportError:\n- from pkgutil import extend_path\n-\n- __path__ = extend_path(__path__, __name__)\n+__import__("pkg_resources").declare_namespace(__name__)\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-12T15:14:37+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/bc47ad8c01aa4963058f747c41031de7b3d44882 - -Split unit tests and doctests. - -This seems better and clearer anyway, and it avoids needing to call `makeSuite`, which would give: - -``` -DeprecationWarning: unittest.makeSuite() is deprecated and will be removed in Python 3.13. -Please use unittest.TestLoader.loadTestsFromTestCase() instead. -``` - -Verbose output after this change, to check that no tests have gone missing: - -``` -$ tox -e test -- -vvv -... -test: commands[0]> zope-testrunner --all --test-path=/Users/maurits/community/plone-coredev/6.0/src/Products.validation -s Products.validation -vvv -Running tests at all levels -Running zope.testrunner.layer.UnitTests tests: - Set up zope.testrunner.layer.UnitTests in 0.000 seconds. - Running: - ExpressionValidator (Products.validation.validators.ExpressionValidator) (0.010 s) - test_inNumericRange (Products.validation.tests.test_validation.TestValidation.test_inNumericRange) (0.000 s) - test_isDecimal (Products.validation.tests.test_validation.TestValidation.test_isDecimal) (0.000 s) - test_isEmail (Products.validation.tests.test_validation.TestValidation.test_isEmail) (0.000 s) - test_isMailto (Products.validation.tests.test_validation.TestValidation.test_isMailto) (0.000 s) - test_isPrintable (Products.validation.tests.test_validation.TestValidation.test_isPrintable) (0.000 s) - test_isSSN (Products.validation.tests.test_validation.TestValidation.test_isSSN) (0.000 s) - test_isURL (Products.validation.tests.test_validation.TestValidation.test_isURL) (0.000 s) - test_isUSPhoneNumber (Products.validation.tests.test_validation.TestValidation.test_isUSPhoneNumber) (0.000 s) - test_isUnixLikeName (Products.validation.tests.test_validation.TestValidation.test_isUnixLikeName) (0.000 s) - test_isValidId_basic (Products.validation.tests.test_validation.TestValidation.test_isValidId_basic) (0.000 s) - test_isValidId_fallback (Products.validation.tests.test_validation.TestValidation.test_isValidId_fallback) (0.001 s) - test_isValidId_plone (Products.validation.tests.test_validation.TestValidation.test_isValidId_plone) (0.000 s) - Ran 13 tests with 0 failures, 0 errors and 0 skipped in 0.013 seconds. -Tearing down left over layers: - Tear down zope.testrunner.layer.UnitTests in 0.000 seconds. - test: OK (3.08=setup[2.12]+cmd[0.95] seconds) - congratulations :) (3.18 seconds) -``` - -Files changed: -A Products/validation/tests/test_doctests.py -M Products/validation/tests/test_validation.py - -b'diff --git a/Products/validation/tests/test_doctests.py b/Products/validation/tests/test_doctests.py\nnew file mode 100644\nindex 0000000..7b0ad5d\n--- /dev/null\n+++ b/Products/validation/tests/test_doctests.py\n@@ -0,0 +1,14 @@\n+import doctest\n+import unittest\n+\n+\n+def test_suite():\n+ from unittest import TestSuite\n+\n+ suite = TestSuite()\n+\n+ doctests = ("Products.validation.validators.ExpressionValidator",)\n+ for module in doctests:\n+ suite.addTest(doctest.DocTestSuite(module))\n+\n+ return suite\ndiff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex 219120f..ee38248 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -1,6 +1,5 @@\n from Products.validation import validation\n \n-import doctest\n import unittest\n \n \n@@ -190,17 +189,3 @@ def test_isValidId_fallback(self):\n finally:\n if plone_check_id:\n Products.CMFPlone.utils.check_id = plone_check_id\n-\n-\n-def test_suite():\n- from unittest import makeSuite\n- from unittest import TestSuite\n-\n- suite = TestSuite()\n- suite.addTest(makeSuite(TestValidation))\n-\n- doctests = ("Products.validation.validators.ExpressionValidator",)\n- for module in doctests:\n- suite.addTest(doctest.DocTestSuite(module))\n-\n- return suite\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-14T15:07:01+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/0e4110a8792e17393ce5963d4667fb87841f7628 - -Cleanup new test_doctests.py. - -Files changed: -M Products/validation/tests/test_doctests.py - -b'diff --git a/Products/validation/tests/test_doctests.py b/Products/validation/tests/test_doctests.py\nindex 7b0ad5d..aa509c5 100644\n--- a/Products/validation/tests/test_doctests.py\n+++ b/Products/validation/tests/test_doctests.py\n@@ -1,14 +1,11 @@\n+from unittest import TestSuite\n+\n import doctest\n-import unittest\n \n \n def test_suite():\n- from unittest import TestSuite\n-\n suite = TestSuite()\n-\n- doctests = ("Products.validation.validators.ExpressionValidator",)\n- for module in doctests:\n- suite.addTest(doctest.DocTestSuite(module))\n-\n+ suite.addTest(\n+ doctest.DocTestSuite("Products.validation.validators.ExpressionValidator")\n+ )\n return suite\n' - -Repository: Products.validation - - -Branch: refs/heads/master -Date: 2024-08-14T17:23:50+02:00 -Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/Products.validation/commit/63a1fd86123aabeb804dd46810dd22c0c7a5d19a - -Merge pull request #12 from plone/config-with-default-template-4b86f8f1 - -Drop support for Plone 5.2 and for Python 3.7 and lower - -Files changed: -A .editorconfig -A .flake8 -A .github/workflows/meta.yml -A .meta.toml -A .pre-commit-config.yaml -A Products/validation/tests/test_doctests.py -A dependabot.yml -A news/+meta.internal -A news/60.breaking.1 -A news/60.breaking.2 -A tox.ini -M .gitignore -M CHANGES.rst -M Products/__init__.py -M Products/validation/ZService.py -M Products/validation/__init__.py -M Products/validation/chain.py -M Products/validation/config.py -M Products/validation/exceptions.py -M Products/validation/i18n.py -M Products/validation/interfaces/IValidationService.py -M Products/validation/interfaces/IValidator.py -M Products/validation/interfaces/__init__.py -M Products/validation/service.py -M Products/validation/tests/__init__.py -M Products/validation/tests/test_validation.py -M Products/validation/validators/BaseValidators.py -M Products/validation/validators/EmptyValidator.py -M Products/validation/validators/ExpressionValidator.py -M Products/validation/validators/IdValidator.py -M Products/validation/validators/RangeValidator.py -M Products/validation/validators/RegexValidator.py -M Products/validation/validators/SupplValidators.py -M Products/validation/validators/__init__.py -M docs/ChangeLog -M pyproject.toml -M setup.py -D Products/validation/validators/validator.py -D setup.cfg - -b'diff --git a/.editorconfig b/.editorconfig\nnew file mode 100644\nindex 0000000..5b3c112\n--- /dev/null\n+++ b/.editorconfig\n@@ -0,0 +1,56 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+#\n+# EditorConfig Configuration file, for more details see:\n+# http://EditorConfig.org\n+# EditorConfig is a convention description, that could be interpreted\n+# by multiple editors to enforce common coding conventions for specific\n+# file types\n+\n+# top-most EditorConfig file:\n+# Will ignore other EditorConfig files in Home directory or upper tree level.\n+root = true\n+\n+\n+[*]\n+# Default settings for all files.\n+# Unix-style newlines with a newline ending every file\n+end_of_line = lf\n+insert_final_newline = true\n+trim_trailing_whitespace = true\n+# Set default charset\n+charset = utf-8\n+# Indent style default\n+indent_style = space\n+# Max Line Length - a hard line wrap, should be disabled\n+max_line_length = off\n+\n+[*.{py,cfg,ini}]\n+# 4 space indentation\n+indent_size = 4\n+\n+[*.{yml,zpt,pt,dtml,zcml,html,xml}]\n+# 2 space indentation\n+indent_size = 2\n+\n+[*.{json,jsonl,js,jsx,ts,tsx,css,less,scss}]\n+# Frontend development\n+# 2 space indentation\n+indent_size = 2\n+max_line_length = 80\n+\n+[{Makefile,.gitmodules}]\n+# Tab indentation (no size specified, but view as 4 spaces)\n+indent_style = tab\n+indent_size = unset\n+tab_width = unset\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [editorconfig]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/.flake8 b/.flake8\nnew file mode 100644\nindex 0000000..92fca63\n--- /dev/null\n+++ b/.flake8\n@@ -0,0 +1,26 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[flake8]\n+doctests = 1\n+ignore =\n+ # black takes care of line length\n+ E501,\n+ # black takes care of where to break lines\n+ W503,\n+ # black takes care of spaces within slicing (list[:])\n+ E203,\n+ # black takes care of spaces after commas\n+ E231,\n+per-file-ignores =\n+ Products/validation/__init__.py:F401\n+ Products/validation/interfaces/__init__.py:F401\n+ Products/validation/validators/validator.py:F401\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [flake8]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/.github/workflows/meta.yml b/.github/workflows/meta.yml\nnew file mode 100644\nindex 0000000..c9848bc\n--- /dev/null\n+++ b/.github/workflows/meta.yml\n@@ -0,0 +1,75 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+name: Meta\n+on:\n+ push:\n+ branches:\n+ - master\n+ - main\n+ pull_request:\n+ branches:\n+ - master\n+ - main\n+ workflow_dispatch:\n+\n+##\n+# To set environment variables for all jobs, add in .meta.toml:\n+# [github]\n+# env = """\n+# debug: 1\n+# image-name: \'org/image\'\n+# image-tag: \'latest\'\n+# """\n+##\n+\n+jobs:\n+ qa:\n+ uses: plone/meta/.github/workflows/qa.yml@main\n+ test:\n+ uses: plone/meta/.github/workflows/test.yml@main\n+ coverage:\n+ uses: plone/meta/.github/workflows/coverage.yml@main\n+ dependencies:\n+ uses: plone/meta/.github/workflows/dependencies.yml@main\n+ release_ready:\n+ uses: plone/meta/.github/workflows/release_ready.yml@main\n+ circular:\n+ uses: plone/meta/.github/workflows/circular.yml@main\n+\n+##\n+# To modify the list of default jobs being created add in .meta.toml:\n+# [github]\n+# jobs = [\n+# "qa",\n+# "test",\n+# "coverage",\n+# "dependencies",\n+# "release_ready",\n+# "circular",\n+# ]\n+##\n+\n+##\n+# To request that some OS level dependencies get installed\n+# when running tests/coverage jobs, add in .meta.toml:\n+# [github]\n+# os_dependencies = "git libxml2 libxslt"\n+##\n+\n+##\n+# To test against a specific matrix of python versions\n+# when running tests jobs, add in .meta.toml:\n+# [github]\n+# py_versions = "[\'3.12\', \'3.11\']"\n+##\n+\n+\n+##\n+# Specify additional jobs in .meta.toml:\n+# [github]\n+# extra_lines = """\n+# another:\n+# uses: org/repo/.github/workflows/file.yml@main\n+# """\n+##\ndiff --git a/.gitignore b/.gitignore\nindex 1a4e837..486392f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -1,9 +1,56 @@\n-bin/\n-lib*\n-include/\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+# python related\n *.egg-info\n-.installed.cfg\n-bootstrap.py\n+*.pyc\n+*.pyo\n+\n+# translation related\n+*.mo\n+\n+# tools related\n+build/\n+.coverage\n+.*project\n+coverage.xml\n+dist/\n+docs/_build\n+__pycache__/\n+.tox\n+.vscode/\n+node_modules/\n+\n+# venv / buildout related\n+bin/\n develop-eggs/\n-*.py[co]\n-pip-selfcheck.json\n+eggs/\n+.eggs/\n+etc/\n+.installed.cfg\n+include/\n+lib/\n+lib64\n+.mr.developer.cfg\n+parts/\n+pyvenv.cfg\n+var/\n+local.cfg\n+\n+# mxdev\n+/instance/\n+/.make-sentinels/\n+/*-mxdev.txt\n+/reports/\n+/sources/\n+/venv/\n+.installed.txt\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [gitignore]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/.meta.toml b/.meta.toml\nnew file mode 100644\nindex 0000000..20c2d9e\n--- /dev/null\n+++ b/.meta.toml\n@@ -0,0 +1,17 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[meta]\n+template = "default"\n+commit-id = "4b86f8f1"\n+\n+[flake8]\n+extra_lines = """\n+per-file-ignores =\n+ Products/validation/__init__.py:F401\n+ Products/validation/interfaces/__init__.py:F401\n+ Products/validation/validators/validator.py:F401\n+"""\n+\n+[pyproject]\n+dependencies_ignores = "[\'Products.CMFPlone\']"\ndiff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml\nnew file mode 100644\nindex 0000000..3479cc2\n--- /dev/null\n+++ b/.pre-commit-config.yaml\n@@ -0,0 +1,94 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+ci:\n+ autofix_prs: false\n+ autoupdate_schedule: monthly\n+\n+repos:\n+- repo: https://github.com/asottile/pyupgrade\n+ rev: v3.16.0\n+ hooks:\n+ - id: pyupgrade\n+ args: [--py38-plus]\n+- repo: https://github.com/pycqa/isort\n+ rev: 5.13.2\n+ hooks:\n+ - id: isort\n+- repo: https://github.com/psf/black\n+ rev: 24.4.2\n+ hooks:\n+ - id: black\n+- repo: https://github.com/collective/zpretty\n+ rev: 3.1.0\n+ hooks:\n+ - id: zpretty\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# zpretty_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+- repo: https://github.com/PyCQA/flake8\n+ rev: 7.1.0\n+ hooks:\n+ - id: flake8\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# flake8_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+- repo: https://github.com/codespell-project/codespell\n+ rev: v2.3.0\n+ hooks:\n+ - id: codespell\n+ additional_dependencies:\n+ - tomli\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# codespell_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+- repo: https://github.com/mgedmin/check-manifest\n+ rev: "0.49"\n+ hooks:\n+ - id: check-manifest\n+- repo: https://github.com/regebro/pyroma\n+ rev: "4.2"\n+ hooks:\n+ - id: pyroma\n+- repo: https://github.com/mgedmin/check-python-versions\n+ rev: "0.22.0"\n+ hooks:\n+ - id: check-python-versions\n+ args: [\'--only\', \'setup.py,pyproject.toml\']\n+- repo: https://github.com/collective/i18ndude\n+ rev: "6.2.0"\n+ hooks:\n+ - id: i18ndude\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# i18ndude_extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pre_commit]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/CHANGES.rst b/CHANGES.rst\nindex 3b90426..92a4787 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -89,7 +89,7 @@ Bug fixes:\n [hannosch]\n \n - Purge old zope2 Interface interfaces for Zope 2.12 compatibility.\n- Consider branching before this checkin if release required before Plone 4.\n+ Consider branching before this commit if release required before Plone 4.\n [elro]\n \n - Cleaned up lots of old BBB code.\n@@ -161,7 +161,7 @@ Bug fixes:\n Archetypes 1.4.2 (used for Plone 2.5.2) of PortalTransforms is then the 1.5\n final.\n We dont need increasing of release numbers because of Plone 3.0,\n- Archetypes 1.5, ... if theres no change in the dependend product, like\n+ Archetypes 1.5, ... if there\'s no change in the dependent product, like\n this one.\n [jensens]\n \ndiff --git a/Products/__init__.py b/Products/__init__.py\nindex f48ad10..5284146 100644\n--- a/Products/__init__.py\n+++ b/Products/__init__.py\n@@ -1,6 +1 @@\n-# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages\n-try:\n- __import__(\'pkg_resources\').declare_namespace(__name__)\n-except ImportError:\n- from pkgutil import extend_path\n- __path__ = extend_path(__path__, __name__)\n+__import__("pkg_resources").declare_namespace(__name__)\ndiff --git a/Products/validation/ZService.py b/Products/validation/ZService.py\nindex c3aefc1..c3e8ced 100644\n--- a/Products/validation/ZService.py\n+++ b/Products/validation/ZService.py\n@@ -6,16 +6,18 @@\n \n \n # make validator service public\n-security = ModuleSecurityInfo(\'Products.validation.config\')\n-security.declarePublic(\'validation\')\n+security = ModuleSecurityInfo("Products.validation.config")\n+security.declarePublic("validation")\n+\n \n class ZService(Service, Implicit):\n """Service running in a zope site - exposes some methods"""\n \n security = ClassSecurityInfo()\n \n- security.declarePublic(\'validate\')\n- security.declarePublic(\'__call__\')\n- security.declarePublic(\'validatorFor\')\n+ security.declarePublic("validate")\n+ security.declarePublic("__call__")\n+ security.declarePublic("validatorFor")\n+\n \n InitializeClass(ZService)\ndiff --git a/Products/validation/__init__.py b/Products/validation/__init__.py\nindex f32bf23..ffad458 100644\n--- a/Products/validation/__init__.py\n+++ b/Products/validation/__init__.py\n@@ -1,7 +1,7 @@\n-from Products.validation.config import validation\n-from Products.validation.chain import ValidationChain\n from Products.validation.chain import V_REQUIRED\n from Products.validation.chain import V_SUFFICIENT\n-from Products.validation.exceptions import UnknowValidatorError\n-from Products.validation.exceptions import FalseValidatorError\n+from Products.validation.chain import ValidationChain\n+from Products.validation.config import validation\n from Products.validation.exceptions import AlreadyRegisteredValidatorError\n+from Products.validation.exceptions import FalseValidatorError\n+from Products.validation.exceptions import UnknowValidatorError\ndiff --git a/Products/validation/chain.py b/Products/validation/chain.py\nindex 6319feb..9ba9e98 100644\n--- a/Products/validation/chain.py\n+++ b/Products/validation/chain.py\n@@ -4,19 +4,17 @@\n from zope.interface import implementer\n \n \n-V_REQUIRED = 1\n+V_REQUIRED = 1\n V_SUFFICIENT = 2\n-TupleType = type((0,1))\n+TupleType = type((0, 1))\n ListType = type([])\n \n \n @implementer(IValidationChain)\n class ValidationChain:\n- """\n- """\n+ """ """\n \n- def __init__(self, name, title=\'\', description=\'\', validators=(),\n- register=False):\n+ def __init__(self, name, title="", description="", validators=(), register=False):\n self.name = name\n self.title = title or name\n self.description = description\n@@ -24,7 +22,7 @@ def __init__(self, name, title=\'\', description=\'\', validators=(),\n self._chain = []\n \n if type(validators) not in (TupleType, ListType):\n- validators = (validators, )\n+ validators = (validators,)\n for validator in validators:\n if type(validator) in (TupleType, ListType):\n self.append(validator[0], validator[1])\n@@ -35,72 +33,75 @@ def __init__(self, name, title=\'\', description=\'\', validators=(),\n validationService.register(self)\n \n def __repr__(self):\n- """print obj support\n- """\n- map = { V_REQUIRED : \'V_REQUIRED\', V_SUFFICIENT : \'V_SUFFICIENT\' }\n+ """print obj support"""\n+ map = {V_REQUIRED: "V_REQUIRED", V_SUFFICIENT: "V_SUFFICIENT"}\n val = []\n for validator, mode in self:\n name = validator.name\n- val.append("(\'%s\', %s)" % (name, map.get(mode)))\n- return \'(%s)\' % \', \'.join(val)\n+ val.append(f"(\'{name}\', {map.get(mode)})")\n+ return "(%s)" % ", ".join(val)\n \n def __len__(self):\n- """len(obj) support\n- """\n+ """len(obj) support"""\n return len(self._chain)\n \n def __iter__(self):\n- """Python 2.3 for i in x support\n- """\n+ """Python 2.3 for i in x support"""\n return iter(zip(self._chain, self._v_mode))\n \n- def __cmp__(self, key):\n- if isinstance(key, ValidationChain):\n- str = repr(key)\n+ def __eq__(self, other):\n+ if isinstance(other, ValidationChain):\n+ value = repr(other)\n else:\n- str = key\n- return cmp(repr(self), str)\n+ value = other\n+ return repr(self) == value\n+\n+ def __lt__(self, other):\n+ if isinstance(other, ValidationChain):\n+ value = repr(other)\n+ else:\n+ value = other\n+ return repr(self) < value\n+\n+ def __gt__(self, other):\n+ if isinstance(other, ValidationChain):\n+ value = repr(other)\n+ else:\n+ value = other\n+ return repr(self) > value\n \n def __getitem__(self, idx):\n- """self[idx] support and Python 2.1 for i in x support\n- """\n+ """self[idx] support and Python 2.1 for i in x support"""\n return self._chain[idx], self._v_mode[idx]\n \n def append(self, id_or_obj, mode=V_REQUIRED):\n- """Appends a validator\n- """\n+ """Appends a validator"""\n validator = self.setValidator(id_or_obj)\n self.setMode(validator, mode)\n \n def appendRequired(self, id_or_obj):\n- """Appends a validator as required\n- """\n+ """Appends a validator as required"""\n self.append(id_or_obj, mode=V_REQUIRED)\n \n def appendSufficient(self, id_or_obj):\n- """Appends a validator as sufficient\n- """\n+ """Appends a validator as sufficient"""\n self.append(id_or_obj, mode=V_SUFFICIENT)\n \n def insert(self, id_or_obj, mode=V_REQUIRED, position=0):\n- """Inserts a validator at position (default 0)\n- """\n+ """Inserts a validator at position (default 0)"""\n validator = self.setValidator(id_or_obj, position=position)\n self.setMode(validator, mode, position=position)\n \n def insertRequired(self, id_or_obj, position=0):\n- """Inserts a validator as required at position (default 0)\n- """\n+ """Inserts a validator as required at position (default 0)"""\n self.insert(id_or_obj, mode=V_REQUIRED, position=0)\n \n def insertSufficient(self, id_or_obj, position=0):\n- """Inserts a validator as sufficient at position (default 0)\n- """\n+ """Inserts a validator as sufficient at position (default 0)"""\n self.insert(id_or_obj, mode=V_SUFFICIENT, position=0)\n \n def setMode(self, validator, mode, position=None):\n- """Set mode\n- """\n+ """Set mode"""\n # validator not required\n if position is None:\n self._v_mode.append(mode)\n@@ -108,8 +109,7 @@ def setMode(self, validator, mode, position=None):\n self._v_mode.insert(position, mode)\n \n def setValidator(self, id_or_obj, position=None):\n- """Set validator\n- """\n+ """Set validator"""\n validator = validationService.validatorFor(id_or_obj)\n \n if position is None:\n@@ -120,8 +120,7 @@ def setValidator(self, id_or_obj, position=None):\n return validator\n \n def __call__(self, value, *args, **kwargs):\n- """Do validation\n- """\n+ """Do validation"""\n results = {}\n failed = False\n if len(self) == 1:\n@@ -130,38 +129,40 @@ def __call__(self, value, *args, **kwargs):\n # There is only one validator and its mode is\n # \'sufficient\' which means it does not have to\n # validate. So we cut the validation short.\n- return True # validation was successful\n+ return True # validation was successful\n \n for validator, mode in self:\n name = validator.name\n result = validator(value, *args, **kwargs)\n- if result == True:\n+ if result is True:\n if mode == V_SUFFICIENT:\n- return True # validation was successful\n+ return True # validation was successful\n elif mode == V_REQUIRED:\n- continue # go on\n+ continue # go on\n else:\n- raise ValidatorError(\'Unknown mode {0}\'.format(mode))\n+ raise ValidatorError(f"Unknown mode {mode}")\n else:\n if mode == V_SUFFICIENT:\n- if isinstance(result, basestring):\n+ if isinstance(result, (str, bytes)):\n # don\'t log if validator doesn\'t return an error msg\n results[name] = result\n- continue # no fatal error, go on\n+ continue # no fatal error, go on\n elif mode == V_REQUIRED:\n- if isinstance(result, basestring):\n+ if isinstance(result, (str, bytes)):\n # don\'t log if validator doesn\'t return an error msg\n results[name] = result\n failed = True\n- break # fatal error, stop and fail\n+ break # fatal error, stop and fail\n else:\n- raise ValidatorError(\'Unknown mode {0}\'.format(mode))\n+ raise ValidatorError(f"Unknown mode {mode}")\n \n if failed:\n- return \'\\n\'.join([\n- #\'%s: %s\' % (name, res)\n- \'%s\' % res\n- for name, res in results.items()]\n- )\n+ return "\\n".join(\n+ [\n+ # \'%s: %s\' % (name, res)\n+ "%s" % res\n+ for name, res in results.items()\n+ ]\n+ )\n else:\n return True\ndiff --git a/Products/validation/config.py b/Products/validation/config.py\nindex 5d4ae44..9b5a7ec 100644\n--- a/Products/validation/config.py\n+++ b/Products/validation/config.py\n@@ -1,6 +1,7 @@\n from Products.validation.validators import initialize\n from Products.validation.ZService import ZService as Service\n \n+\n validation = Service()\n \n initialize(validation)\ndiff --git a/Products/validation/exceptions.py b/Products/validation/exceptions.py\nindex 1e7b79f..eb2ab8d 100644\n--- a/Products/validation/exceptions.py\n+++ b/Products/validation/exceptions.py\n@@ -1,4 +1,14 @@\n-class ValidatorError(Exception): pass\n-class UnknowValidatorError(ValidatorError): pass\n-class FalseValidatorError(ValidatorError): pass\n-class AlreadyRegisteredValidatorError(ValidatorError): pass\n\\ No newline at end of file\n+class ValidatorError(Exception):\n+ pass\n+\n+\n+class UnknowValidatorError(ValidatorError):\n+ pass\n+\n+\n+class FalseValidatorError(ValidatorError):\n+ pass\n+\n+\n+class AlreadyRegisteredValidatorError(ValidatorError):\n+ pass\ndiff --git a/Products/validation/i18n.py b/Products/validation/i18n.py\nindex f01953c..8e2f6a8 100644\n--- a/Products/validation/i18n.py\n+++ b/Products/validation/i18n.py\n@@ -1,25 +1,13 @@\n-from zope.i18nmessageid import MessageFactory\n from zope.i18n import translate\n from zope.i18nmessageid import Message\n+from zope.i18nmessageid import MessageFactory\n \n-import six\n \n+PloneMessageFactory = MessageFactory("plone")\n \n-PloneMessageFactory = MessageFactory(\'plone\')\n \n-if six.PY2:\n- def safe_unicode(value):\n- if isinstance(value, unicode):\n- return value\n- elif isinstance(value, str):\n- try:\n- return unicode(value, \'utf-8\')\n- except UnicodeDecodeError:\n- return unicode(value, \'utf-8\', \'ignore\')\n- return str(value)\n-else:\n- def safe_unicode(value):\n- return value\n+def safe_unicode(value):\n+ return value\n \n \n def recursiveTranslate(message, **kwargs):\n@@ -27,12 +15,12 @@ def recursiveTranslate(message, **kwargs):\n if kwargs[\'REQUEST\'] is None, return the message untranslated\n """\n \n- request = kwargs.get(\'REQUEST\',None)\n+ request = kwargs.get("REQUEST", None)\n \n map = message.mapping\n if map:\n for key in map.keys():\n- if type(map[key]) == Message:\n+ if isinstance(map[key], Message):\n map[key] = translate(map[key], context=request)\n \n return translate(message, context=request)\ndiff --git a/Products/validation/interfaces/IValidationService.py b/Products/validation/interfaces/IValidationService.py\nindex 8d9a6b2..8c3aa64 100644\n--- a/Products/validation/interfaces/IValidationService.py\n+++ b/Products/validation/interfaces/IValidationService.py\n@@ -1,4 +1,5 @@\n-from zope.interface import Interface, Attribute\n+from zope.interface import Interface\n+\n \n class IValidationService(Interface):\n \n@@ -12,4 +13,4 @@ def register(validator):\n """load a validator for access by name"""\n \n def unregister(name_or_validator):\n- """unregisters a validator by name"""\n\\ No newline at end of file\n+ """unregisters a validator by name"""\ndiff --git a/Products/validation/interfaces/IValidator.py b/Products/validation/interfaces/IValidator.py\nindex 5193765..d890f26 100644\n--- a/Products/validation/interfaces/IValidator.py\n+++ b/Products/validation/interfaces/IValidator.py\n@@ -1,4 +1,6 @@\n-from zope.interface import Interface, Attribute\n+from zope.interface import Attribute\n+from zope.interface import Interface\n+\n \n class IValidator(Interface):\n \n@@ -11,5 +13,4 @@ def __call__(value, *args, **kwargs):\n \n \n class IValidationChain(IValidator):\n- """Marker interface for a chain\n- """\n+ """Marker interface for a chain"""\ndiff --git a/Products/validation/interfaces/__init__.py b/Products/validation/interfaces/__init__.py\nindex 28454b5..9d3b313 100644\n--- a/Products/validation/interfaces/__init__.py\n+++ b/Products/validation/interfaces/__init__.py\n@@ -1,3 +1,5 @@\n # backward compatibility\n+from Products.validation.interfaces.IValidationService import (\n+ IValidationService as ivalidationService,\n+)\n from Products.validation.interfaces.IValidator import IValidator as ivalidator\n-from Products.validation.interfaces.IValidationService import IValidationService as ivalidationService\ndiff --git a/Products/validation/service.py b/Products/validation/service.py\nindex 898fdd6..6243711 100644\n--- a/Products/validation/service.py\n+++ b/Products/validation/service.py\n@@ -1,12 +1,9 @@\n from Products.validation.exceptions import FalseValidatorError\n from Products.validation.exceptions import UnknowValidatorError\n-from Products.validation.exceptions import AlreadyRegisteredValidatorError\n from Products.validation.interfaces.IValidationService import IValidationService\n from Products.validation.interfaces.IValidator import IValidator\n from zope.interface import implementer\n \n-import six\n-\n \n @implementer(IValidationService)\n class Service:\n@@ -31,13 +28,13 @@ def validatorFor(self, name_or_validator):\n else:\n raise FalseValidatorError(name_or_validator)\n \n- def register(self, validator): #XXX\n+ def register(self, validator):\n if not IValidator.providedBy(validator):\n raise FalseValidatorError(validator)\n name = validator.name\n # The following code prevents refreshing\n- ##if self._validator.has_key(name):\n- ## raise AlreadyRegisteredValidatorError, name\n+ # if self._validator.has_key(name):\n+ # raise AlreadyRegisteredValidatorError, name\n self._validator[name] = validator\n \n def items(self):\ndiff --git a/Products/validation/tests/__init__.py b/Products/validation/tests/__init__.py\nindex d310fdd..5bb534f 100644\n--- a/Products/validation/tests/__init__.py\n+++ b/Products/validation/tests/__init__.py\n@@ -1 +1 @@\n-# package\n\\ No newline at end of file\n+# package\ndiff --git a/Products/validation/tests/test_doctests.py b/Products/validation/tests/test_doctests.py\nnew file mode 100644\nindex 0000000..aa509c5\n--- /dev/null\n+++ b/Products/validation/tests/test_doctests.py\n@@ -0,0 +1,11 @@\n+from unittest import TestSuite\n+\n+import doctest\n+\n+\n+def test_suite():\n+ suite = TestSuite()\n+ suite.addTest(\n+ doctest.DocTestSuite("Products.validation.validators.ExpressionValidator")\n+ )\n+ return suite\ndiff --git a/Products/validation/tests/test_validation.py b/Products/validation/tests/test_validation.py\nindex ddfb7dc..ee38248 100644\n--- a/Products/validation/tests/test_validation.py\n+++ b/Products/validation/tests/test_validation.py\n@@ -1,14 +1,12 @@\n-# -*- coding: utf-8 -*-\n from Products.validation import validation\n \n-import doctest\n-import six\n import unittest\n \n \n-class Dummy(object):\n+class Dummy:\n """Dummy object with basic zope-like containment."""\n- portal_type = \'dummy\' # needed in Plone 5.2 test\n+\n+ portal_type = "dummy" # needed in Plone 5.2 test\n \n def __init__(self, _id=None):\n self.id = _id\n@@ -38,139 +36,138 @@ def __getattr__(self, name, default=None):\n if obj.getId() == name:\n return obj\n return default\n- return super(Dummy, self).__getattr__(name, default)\n+ return super().__getattr__(name, default)\n \n def dummy_checker(self, _id, **kwargs):\n- if _id == \'good\':\n+ if _id == "good":\n return 1\n- return \'bad id\'\n+ return "bad id"\n \n \n class TestValidation(unittest.TestCase):\n \n def test_inNumericRange(self):\n- v = validation.validatorFor(\'inNumericRange\')\n+ v = validation.validatorFor("inNumericRange")\n self.assertEqual(v(10, 1, 20), 1)\n- self.assertEqual(v(\'10\', 1, 20), 1)\n- self.assertEqual(v(0, 4, 5), u"Validation failed(inNumericRange): \'0\' out of range(4, 5)")\n+ self.assertEqual(v("10", 1, 20), 1)\n+ self.assertEqual(\n+ v(0, 4, 5), "Validation failed(inNumericRange): \'0\' out of range(4, 5)"\n+ )\n \n def test_isDecimal(self):\n- v = validation.validatorFor(\'isDecimal\')\n- self.assertEqual(v(\'1.5\'), 1)\n- self.assertEqual(v(\'1,5\'), 1)\n- self.assertEqual(v(\'NaN\'), u"Validation failed(isDecimal): \'NaN\' is not a decimal number.")\n+ v = validation.validatorFor("isDecimal")\n+ self.assertEqual(v("1.5"), 1)\n+ self.assertEqual(v("1,5"), 1)\n+ self.assertEqual(\n+ v("NaN"), "Validation failed(isDecimal): \'NaN\' is not a decimal number."\n+ )\n \n def test_isPrintable(self):\n- v = validation.validatorFor(\'isPrintable\')\n- self.assertEqual(v(\'text\'), 1)\n+ v = validation.validatorFor("isPrintable")\n+ self.assertEqual(v("text"), 1)\n self.assertEqual(\n- v(\'\\\\u203\'),\n- u"Validation failed(isPrintable): \'\\\\u203\' contains unprintable characters"\n+ v("\\\\u203"),\n+ "Validation failed(isPrintable): \'\\\\u203\' contains unprintable characters",\n )\n- if six.PY3:\n- int_type = ""\n- else:\n- int_type = ""\n+ int_type = ""\n self.assertEqual(\n v(10),\n- u"Validation failed(isPrintable): 10 of type {0}, expected \'string\'".format(int_type)\n+ f"Validation failed(isPrintable): 10 of type {int_type}, expected \'string\'",\n )\n \n def test_isSSN(self):\n- v = validation.validatorFor(\'isSSN\')\n- self.assertEqual(v(\'111223333\'), 1)\n- self.assertEqual(v(\'111-22-3333\', ignore=r\'-\'), 1)\n+ v = validation.validatorFor("isSSN")\n+ self.assertEqual(v("111223333"), 1)\n+ self.assertEqual(v("111-22-3333", ignore=r"-"), 1)\n \n def test_isUSPhoneNumber(self):\n- v = validation.validatorFor(\'isUSPhoneNumber\')\n- self.assertEqual(v(\'(212) 555-1212\',\n- ignore=r\'[\\s\\(\\)\\-]\'), 1)\n- self.assertEqual(v(\'2125551212\',\n- ignore=r\'[\\s\\(\\)\\-]\'), 1)\n+ v = validation.validatorFor("isUSPhoneNumber")\n+ self.assertEqual(v("(212) 555-1212", ignore=r"[\\s\\(\\)\\-]"), 1)\n+ self.assertEqual(v("2125551212", ignore=r"[\\s\\(\\)\\-]"), 1)\n \n- self.assertEqual(v(\'(212) 555-1212\'), 1)\n+ self.assertEqual(v("(212) 555-1212"), 1)\n \n def test_isURL(self):\n- v = validation.validatorFor(\'isURL\')\n- self.assertEqual(v(\'http://foo.bar:8080/manage\'), 1)\n- self.assertEqual(v(\'https://foo.bar:8080/manage\'), 1)\n- self.assertEqual(v(\'https://be.brussels:8080/manage\'), 1)\n- self.assertEqual(v(\'irc://tiran@irc.freenode.net:6667/#plone\'), 1)\n- self.assertEqual(v(\'fish://tiran:password@myserver/~/\'), 1)\n- self.assertEqual(v(\'http://\\n\'), u"Validation failed(isURL): \'http://\\n\' is not a valid url.")\n- self.assertNotEqual(v(\'../foo/bar\'), 1)\n+ v = validation.validatorFor("isURL")\n+ self.assertEqual(v("http://foo.bar:8080/manage"), 1)\n+ self.assertEqual(v("https://foo.bar:8080/manage"), 1)\n+ self.assertEqual(v("https://be.brussels:8080/manage"), 1)\n+ self.assertEqual(v("irc://tiran@irc.freenode.net:6667/#plone"), 1)\n+ self.assertEqual(v("fish://tiran:password@myserver/~/"), 1)\n+ self.assertEqual(\n+ v("http://\\n"), "Validation failed(isURL): \'http://\\n\' is not a valid url."\n+ )\n+ self.assertNotEqual(v("../foo/bar"), 1)\n \n def test_isEmail(self):\n- v = validation.validatorFor(\'isEmail\')\n- self.assertEqual(v(\'test@test.com\'), 1)\n- self.assertEqual(v(\'test@be.brussels\'), 1)\n- self.assertNotEqual(v(\'@foo.bar\'), 1)\n- self.assertEqual(v(\'me\'), u"Validation failed(isEmail): \'me\' is not a valid email address.")\n+ v = validation.validatorFor("isEmail")\n+ self.assertEqual(v("test@test.com"), 1)\n+ self.assertEqual(v("test@be.brussels"), 1)\n+ self.assertNotEqual(v("@foo.bar"), 1)\n+ self.assertEqual(\n+ v("me"), "Validation failed(isEmail): \'me\' is not a valid email address."\n+ )\n \n def test_isMailto(self):\n- v = validation.validatorFor(\'isMailto\')\n- self.assertEqual(v(\'mailto:test@test.com\'), 1)\n- self.assertEqual(v(\'mailto:test@be.brussels\'), 1)\n- self.assertNotEqual(v(\'test@test.com\'), 1)\n- self.assertNotEqual(v(\'mailto:@foo.bar\'), 1)\n- self.assertNotEqual(v(\'@foo.bar\'), 1)\n- self.assertNotEqual(v(\'mailto:\'), 1)\n- self.assertEqual(v(\'me\'), u"Validation failed(isMailto): \'me\' is not a valid email address.")\n+ v = validation.validatorFor("isMailto")\n+ self.assertEqual(v("mailto:test@test.com"), 1)\n+ self.assertEqual(v("mailto:test@be.brussels"), 1)\n+ self.assertNotEqual(v("test@test.com"), 1)\n+ self.assertNotEqual(v("mailto:@foo.bar"), 1)\n+ self.assertNotEqual(v("@foo.bar"), 1)\n+ self.assertNotEqual(v("mailto:"), 1)\n+ self.assertEqual(\n+ v("me"), "Validation failed(isMailto): \'me\' is not a valid email address."\n+ )\n \n def test_isUnixLikeName(self):\n- v = validation.validatorFor(\'isUnixLikeName\')\n- self.assertEqual(v(\'abcd\'), 1)\n- self.assertTrue(v(\'a_123456\'), 1)\n- self.assertNotEqual(v(\'123\'), 1)\n- self.assertNotEqual(v(\'ab.c\'), 1)\n- self.assertEqual(v(\'ab,c\'), u"Validation failed(isUnixLikeName): \'ab,c\' this name is not a valid identifier")\n- self.assertNotEqual(v(\'aaaaaaaab\'), 1) # too long\n+ v = validation.validatorFor("isUnixLikeName")\n+ self.assertEqual(v("abcd"), 1)\n+ self.assertTrue(v("a_123456"), 1)\n+ self.assertNotEqual(v("123"), 1)\n+ self.assertNotEqual(v("ab.c"), 1)\n+ self.assertEqual(\n+ v("ab,c"),\n+ "Validation failed(isUnixLikeName): \'ab,c\' this name is not a valid identifier",\n+ )\n+ self.assertNotEqual(v("aaaaaaaab"), 1) # too long\n \n def test_isValidId_basic(self):\n- from Products.validation.validators import IdValidator\n-\n- v = validation.validatorFor(\'isValidId\')\n- obj = Dummy(\'foo\')\n+ v = validation.validatorFor("isValidId")\n+ obj = Dummy("foo")\n \n # Use a specific checker.\n obj.check_id = obj.dummy_checker\n- self.assertEqual(v(\'good\', obj), 1)\n- self.assertEqual(v(\'a b\', obj), \'bad id\')\n+ self.assertEqual(v("good", obj), 1)\n+ self.assertEqual(v("a b", obj), "bad id")\n \n def test_isValidId_plone(self):\n- from Products.validation.validators import IdValidator\n try:\n- from Products.CMFPlone.utils import check_id\n+ # We do not use this import directly, but if it fails,\n+ # this test is useless.\n+ from Products.CMFPlone.utils import check_id # noqa F401\n except ImportError:\n return\n \n- v = validation.validatorFor(\'isValidId\')\n- obj = Dummy(\'foo\')\n- obj2 = Dummy(\'foo2\')\n- parent = Dummy(\'parent\')\n+ v = validation.validatorFor("isValidId")\n+ obj = Dummy("foo")\n+ obj2 = Dummy("foo2")\n+ parent = Dummy("parent")\n parent.add(obj)\n parent.add(obj2)\n \n- self.assertEqual(v(\'good\', obj), 1)\n- self.assertEqual(v(\'foo\', obj), 1)\n+ self.assertEqual(v("good", obj), 1)\n+ self.assertEqual(v("foo", obj), 1)\n # This error message would be translated usually, but we do not care.\n self.assertEqual(\n- v(\'foo\', obj2),\n- u\'There is already an item named ${name} in this folder.\')\n+ v("foo", obj2), "There is already an item named ${name} in this folder."\n+ )\n # Plone seems to allow spaces.\n- self.assertEqual(v(\'a b\', obj), 1)\n+ self.assertEqual(v("a b", obj), 1)\n # Some ids are forbidden in Plone. We get an i18n message back.\n- # Problem: on Plone 5.1, utils.check_id simply looks for a\n- # check_id script/attribute on the context. This will fail.\n- # So only test this in Plone 5.2+, not on 5.1.\n- import pkg_resources\n- version = pkg_resources.get_distribution(\'Products.CMFPlone\').version\n- if version.startswith(\'5.1\'):\n- return\n- self.assertEqual(v(\'layout\', obj), \'${name} is reserved.\')\n+ self.assertEqual(v("layout", obj), "${name} is reserved.")\n \n def test_isValidId_fallback(self):\n- from Products.validation.validators import IdValidator\n # We can only check this if utils.check_id gives an ImportError.\n try:\n from Products.CMFPlone.utils import check_id as plone_check_id\n@@ -178,30 +175,17 @@ def test_isValidId_fallback(self):\n plone_check_id = None\n else:\n import Products.CMFPlone.utils\n+\n del Products.CMFPlone.utils.check_id\n \n try:\n- v = validation.validatorFor(\'isValidId\')\n- obj = Dummy(\'foo\')\n- parent = Dummy(\'parent\')\n+ v = validation.validatorFor("isValidId")\n+ obj = Dummy("foo")\n+ parent = Dummy("parent")\n parent.add(obj)\n- self.assertEqual(v(\'good\', obj), 1)\n- self.assertEqual(v(\'foo\', obj), 1)\n- self.assertEqual(v(\'a b\', obj), u\'Spaces are not allowed in ids\')\n+ self.assertEqual(v("good", obj), 1)\n+ self.assertEqual(v("foo", obj), 1)\n+ self.assertEqual(v("a b", obj), "Spaces are not allowed in ids")\n finally:\n if plone_check_id:\n Products.CMFPlone.utils.check_id = plone_check_id\n-\n-\n-def test_suite():\n- from unittest import TestSuite, makeSuite\n- suite = TestSuite()\n- suite.addTest(makeSuite(TestValidation))\n-\n- doctests = (\n- \'Products.validation.validators.ExpressionValidator\',\n- )\n- for module in doctests:\n- suite.addTest(doctest.DocTestSuite(module))\n-\n- return suite\ndiff --git a/Products/validation/validators/BaseValidators.py b/Products/validation/validators/BaseValidators.py\nindex c956e0c..0d1fb24 100644\n--- a/Products/validation/validators/BaseValidators.py\n+++ b/Products/validation/validators/BaseValidators.py\n@@ -1,49 +1,113 @@\n-from Products.validation.validators.RegexValidator import RegexValidator\n-from Products.validation.validators.RangeValidator import RangeValidator\n from Products.validation.i18n import PloneMessageFactory as _\n+from Products.validation.validators.RangeValidator import RangeValidator\n+from Products.validation.validators.RegexValidator import RegexValidator\n+\n \n # protocols for isURL validator, the secure (*s) variants are automagically\n # added\n-protocols = (\'http\', \'ftp\', \'irc\', \'news\', \'imap\', \'gopher\', \'jabber\',\n- \'webdav\', \'smb\', \'fish\', \'ldap\', \'pop3\', \'smtp\', \'sftp\', \'ssh\', \'feed\'\n- )\n+protocols = (\n+ "http",\n+ "ftp",\n+ "irc",\n+ "news",\n+ "imap",\n+ "gopher",\n+ "jabber",\n+ "webdav",\n+ "smb",\n+ "fish",\n+ "ldap",\n+ "pop3",\n+ "smtp",\n+ "sftp",\n+ "ssh",\n+ "feed",\n+)\n \n # email re w/o leading \'^\'\n-EMAIL_RE = "([0-9a-zA-Z_&.\'+-]+!)*[0-9a-zA-Z_&.\'+-]+@(([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-z-A-Z])?\\.)+[a-zA-Z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})$"\n+EMAIL_RE = r"([0-9a-zA-Z_&.\'+-]+!)*[0-9a-zA-Z_&.\'+-]+@(([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-z-A-Z])?\\.)+[a-zA-Z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})$"\n \n baseValidators = [\n- RangeValidator(\'inNumericRange\', title=\'\', description=\'\'),\n- RegexValidator(\'isDecimal\',\n- r\'^([+-]?)(?=\\d|(\\.|\\,)\\d)\\d*((\\,|\\.)\\d*)?([Ee]([+-]?\\d+))?$\',\n- title=\'\', description=\'\',\n- errmsg=_(u\'is not a decimal number.\')),\n- RegexValidator(\'isInt\', r\'^([+-])?\\d+$\', title=\'\', description=\'\',\n- errmsg=_(u\'is not an integer.\')),\n- RegexValidator(\'isPrintable\', r\'[a-zA-Z0-9\\s]+$\', title=\'\', description=\'\',\n- errmsg=_(u\'contains unprintable characters\')),\n- RegexValidator(\'isSSN\', r\'^\\d{9}$\', title=\'\', description=\'\',\n- errmsg=_(u\'is not a well formed SSN.\')),\n- RegexValidator(\'isUSPhoneNumber\', r\'^\\d{10}$\', ignore=\'[\\(\\)\\-\\s]\',\n- title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid us phone number.\')),\n- RegexValidator(\'isInternationalPhoneNumber\', r\'^\\d+$\', ignore=\'[\\(\\)\\-\\s\\+]\',\n- title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid international phone number.\')),\n- RegexValidator(\'isZipCode\', r\'^(\\d{5}|\\d{9})$\',\n- title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid zip code.\')),\n- RegexValidator(\'isURL\', r\'(%s)s?://[^\\s\\r\\n]+\' % \'|\'.join(protocols),\n- title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid url.\')),\n- RegexValidator(\'isEmail\', \'^\'+EMAIL_RE,\n- title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid email address.\')),\n- RegexValidator(\'isMailto\', \'^mailto:\'+EMAIL_RE,\n- title=\'\', description=\'\',\n- errmsg=_(u\'is not a valid email address.\')),\n- RegexValidator(\'isUnixLikeName\', r"^[A-Za-z][\\w\\d\\-\\_]{0,7}$",\n- title="", description="",\n- errmsg=_(u"this name is not a valid identifier")),\n- ]\n+ RangeValidator("inNumericRange", title="", description=""),\n+ RegexValidator(\n+ "isDecimal",\n+ r"^([+-]?)(?=\\d|(\\.|\\,)\\d)\\d*((\\,|\\.)\\d*)?([Ee]([+-]?\\d+))?$",\n+ title="",\n+ description="",\n+ errmsg=_("is not a decimal number."),\n+ ),\n+ RegexValidator(\n+ "isInt",\n+ r"^([+-])?\\d+$",\n+ title="",\n+ description="",\n+ errmsg=_("is not an integer."),\n+ ),\n+ RegexValidator(\n+ "isPrintable",\n+ r"[a-zA-Z0-9\\s]+$",\n+ title="",\n+ description="",\n+ errmsg=_("contains unprintable characters"),\n+ ),\n+ RegexValidator(\n+ "isSSN",\n+ r"^\\d{9}$",\n+ title="",\n+ description="",\n+ errmsg=_("is not a well formed SSN."),\n+ ),\n+ RegexValidator(\n+ "isUSPhoneNumber",\n+ r"^\\d{10}$",\n+ ignore=r"[\\(\\)\\-\\s]",\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid us phone number."),\n+ ),\n+ RegexValidator(\n+ "isInternationalPhoneNumber",\n+ r"^\\d+$",\n+ ignore=r"[\\(\\)\\-\\s\\+]",\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid international phone number."),\n+ ),\n+ RegexValidator(\n+ "isZipCode",\n+ r"^(\\d{5}|\\d{9})$",\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid zip code."),\n+ ),\n+ RegexValidator(\n+ "isURL",\n+ r"(%s)s?://[^\\s\\r\\n]+" % "|".join(protocols),\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid url."),\n+ ),\n+ RegexValidator(\n+ "isEmail",\n+ "^" + EMAIL_RE,\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid email address."),\n+ ),\n+ RegexValidator(\n+ "isMailto",\n+ "^mailto:" + EMAIL_RE,\n+ title="",\n+ description="",\n+ errmsg=_("is not a valid email address."),\n+ ),\n+ RegexValidator(\n+ "isUnixLikeName",\n+ r"^[A-Za-z][\\w\\d\\-\\_]{0,7}$",\n+ title="",\n+ description="",\n+ errmsg=_("this name is not a valid identifier"),\n+ ),\n+]\n \n-__all__ = (\'baseValidators\', )\n+__all__ = ("baseValidators",)\ndiff --git a/Products/validation/validators/EmptyValidator.py b/Products/validation/validators/EmptyValidator.py\nindex 681b68e..948da9c 100644\n--- a/Products/validation/validators/EmptyValidator.py\n+++ b/Products/validation/validators/EmptyValidator.py\n@@ -1,8 +1,9 @@\n-from zope.interface import implementer\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n from Products.validation.interfaces.IValidator import IValidator\n+from zope.interface import implementer\n+\n \n _marker = []\n \n@@ -10,47 +11,52 @@\n @implementer(IValidator)\n class EmptyValidator:\n \n- def __init__(self, name, title=\'\', description=\'\', showError=True):\n+ def __init__(self, name, title="", description="", showError=True):\n self.name = name\n self.title = title or name\n self.description = description\n self.showError = showError\n \n def __call__(self, value, *args, **kwargs):\n- isEmpty = kwargs.get(\'isEmpty\', False)\n- instance = kwargs.get(\'instance\', None)\n- field = kwargs.get(\'field\', None)\n+ isEmpty = kwargs.get("isEmpty", False)\n+ instance = kwargs.get("instance", None)\n+ field = kwargs.get("field", None)\n \n # XXX: This is a temporary fix. Need to be fixed right for AT 2.0\n # content_edit / BaseObject.processForm() calls\n # widget.process_form a second time!\n if instance and field:\n- widget = field.widget\n- request = getattr(instance, \'REQUEST\', None)\n+ widget = field.widget\n+ request = getattr(instance, "REQUEST", None)\n if request and request.form:\n- form = request.form\n- result = widget.process_form(instance, field, form,\n- empty_marker=_marker,\n- emptyReturnsMarker=True)\n+ form = request.form\n+ result = widget.process_form(\n+ instance, field, form, empty_marker=_marker, emptyReturnsMarker=True\n+ )\n if result is _marker or result is None:\n isEmpty = True\n \n if isEmpty:\n return True\n- elif value == \'\' or value is None:\n+ elif value == "" or value is None:\n return True\n else:\n- if getattr(self, \'showError\', False):\n- msg = _(u"Validation failed($name): \'$value\' is not empty.",\n- mapping = { \'name\' : safe_unicode(self.name), \'value\': safe_unicode(value)})\n+ if getattr(self, "showError", False):\n+ msg = _(\n+ "Validation failed($name): \'$value\' is not empty.",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n else:\n return False\n \n-validatorList = [\n- EmptyValidator(\'isEmpty\', title=\'\', description=\'\'),\n- EmptyValidator(\'isEmptyNoError\', title=\'\', description=\'\', showError=False),\n- ]\n \n-__all__ = (\'validatorList\', )\n+validatorList = [\n+ EmptyValidator("isEmpty", title="", description=""),\n+ EmptyValidator("isEmptyNoError", title="", description="", showError=False),\n+]\n \n+__all__ = ("validatorList",)\ndiff --git a/Products/validation/validators/ExpressionValidator.py b/Products/validation/validators/ExpressionValidator.py\nindex b92a595..59fd52f 100644\n--- a/Products/validation/validators/ExpressionValidator.py\n+++ b/Products/validation/validators/ExpressionValidator.py\n@@ -21,24 +21,25 @@\n """\n """\n \n-from Products.validation.interfaces.IValidator import IValidator\n+from Products.PageTemplates.Expressions import getEngine\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n-from Products.PageTemplates.Expressions import getEngine\n-from zope.interface import implementer\n+from Products.validation.interfaces.IValidator import IValidator\n from zope.i18nmessageid import Message\n+from zope.interface import implementer\n+\n \n @implementer(IValidator)\n class ExpressionValidator:\n- """ Validator for TALES Expressions\n+ """Validator for TALES Expressions\n \n Basically, if the expression compiles it\'s a valid expression,\n otherwise it\'s invalid and you get a message saying that the\n expression has errors.\n \n \n- >>> val=ExpressionValidator(\'python: int(value) == 5\')\n+ >>> val=ExpressionValidator("python: int(value) == 5")\n >>> class C:i=1\n >>> c=C()\n >>> val(5,c) is True\n@@ -50,49 +51,51 @@ class ExpressionValidator:\n \n It is also possible to specify the error string\n \n- >>> val=ExpressionValidator(\'python: int(value) == 5\', \'value doesnt match %(value)s\')\n+ >>> val=ExpressionValidator("python: int(value) == 5", "value does not match %(value)s")\n >>> val(4,c)\n- \'value doesnt match 4\'\n+ \'value does not match 4\'\n \n \n """\n \n- name = \'talesexpressionvalidator\'\n+ name = "talesexpressionvalidator"\n \n- def __init__(self,expression=None,errormsg=None):\n- self.expression=expression\n- self.errormsg=errormsg\n+ def __init__(self, expression=None, errormsg=None):\n+ self.expression = expression\n+ self.errormsg = errormsg\n if expression:\n- self.compiledExpression=getEngine().compile(expression)\n+ self.compiledExpression = getEngine().compile(expression)\n \n def __call__(self, value, instance, *args, **kwargs):\n- kw={\n- \'here\':instance,\n- \'object\':instance,\n- \'instance\':instance,\n- \'value\':value,\n- \'args\':args,\n- \'kwargs\':kwargs,\n- }\n-\n- context=getEngine().getContext(kw)\n- res=self.compiledExpression(context)\n+ kw = {\n+ "here": instance,\n+ "object": instance,\n+ "instance": instance,\n+ "value": value,\n+ "args": args,\n+ "kwargs": kwargs,\n+ }\n+\n+ context = getEngine().getContext(kw)\n+ res = self.compiledExpression(context)\n \n if res:\n return True\n else:\n- if self.errormsg and type(self.errormsg) == Message:\n- #hack to support including values in i18n message, too. hopefully this works out\n- #potentially it could unintentionally overwrite already present values\n+ if self.errormsg and isinstance(self.errormsg, Message):\n+ # hack to support including values in i18n message, too. hopefully this works out\n+ # potentially it could unintentionally overwrite already present values\n self.errormsg.mapping = kw\n return recursiveTranslate(self.errormsg, **kwargs)\n elif self.errormsg:\n # support strings as errormsg for backward compatibility\n return self.errormsg % kw\n else:\n- msg = _(u\'validation failed, expr was:$expr\',\n- mapping={\'expr\': safe_unicode(self.expression)})\n+ msg = _(\n+ "validation failed, expr was:$expr",\n+ mapping={"expr": safe_unicode(self.expression)},\n+ )\n return recursiveTranslate(msg, **kwargs)\n \n \n-#validation.register(TALESExpressionValidator())\n+# validation.register(TALESExpressionValidator())\ndiff --git a/Products/validation/validators/IdValidator.py b/Products/validation/validators/IdValidator.py\nindex 1e80a6c..8301ed2 100644\n--- a/Products/validation/validators/IdValidator.py\n+++ b/Products/validation/validators/IdValidator.py\n@@ -1,29 +1,29 @@\n-from Acquisition import aq_parent\n-from Acquisition import aq_inner\n from Acquisition import aq_base\n from Acquisition import aq_get\n-from zExceptions import BadRequest\n+from Acquisition import aq_inner\n+from Acquisition import aq_parent\n from OFS import ObjectManager\n-from Products.validation.interfaces.IValidator import IValidator\n-from zope.interface import implementer\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n+from zExceptions import BadRequest\n+from zope.interface import implementer\n \n \n def fallback_check_id(instance, id, **kwargs):\n # space test\n- if \' \' in id:\n- msg = _(u\'Spaces are not allowed in ids\')\n+ if " " in id:\n+ msg = _("Spaces are not allowed in ids")\n return recursiveTranslate(msg, **kwargs)\n \n # in parent test\n parent = aq_parent(aq_inner(instance))\n # If the id is given to a different object already\n- if (id in parent.objectIds() and\n- getattr(aq_base(parent), id) is not aq_base(instance)):\n- msg = _(u\'Id $id is already in use\',\n- mapping = {\'id\': safe_unicode(id)})\n+ if id in parent.objectIds() and getattr(aq_base(parent), id) is not aq_base(\n+ instance\n+ ):\n+ msg = _("Id $id is already in use", mapping={"id": safe_unicode(id)})\n return recursiveTranslate(msg, **kwargs)\n \n # object manager test\n@@ -39,7 +39,7 @@ def fallback_check_id(instance, id, **kwargs):\n @implementer(IValidator)\n class IdValidator:\n \n- def __init__( self, name, title=\'\', description=\'\'):\n+ def __init__(self, name, title="", description=""):\n self.name = name\n self.title = title or name\n self.description = description\n@@ -49,10 +49,10 @@ def __call__(self, id, instance, *args, **kwargs):\n # for example a Python skin script or a method,\n # like Products/CMFPlone/skins/plone_scripts/check_id.py\n # until Plone 5.1.\n- check_id = aq_get(instance, \'check_id\', None, 1)\n+ check_id = aq_get(instance, "check_id", None, 1)\n if check_id is not None:\n # instance is passed implicitly: it is \'self\'\n- result = check_id(id, required=kwargs.get(\'required\'))\n+ result = check_id(id, required=kwargs.get("required"))\n else:\n try:\n # try to use the check_id script of CMFPlone\n@@ -66,12 +66,12 @@ def __call__(self, id, instance, *args, **kwargs):\n result = check_id(instance, id, **kwargs)\n else:\n # Only the \'required\' keyword is accepted.\n- result = check_id(instance, id, required=kwargs.get(\'required\'))\n+ result = check_id(instance, id, required=kwargs.get("required"))\n return result or 1\n \n \n validatorList = [\n- IdValidator(\'isValidId\', title=\'\', description=\'\'),\n- ]\n+ IdValidator("isValidId", title="", description=""),\n+]\n \n-__all__ = (\'validatorList\', )\n+__all__ = ("validatorList",)\ndiff --git a/Products/validation/validators/RangeValidator.py b/Products/validation/validators/RangeValidator.py\nindex 262ffbf..ba1fed2 100644\n--- a/Products/validation/validators/RangeValidator.py\n+++ b/Products/validation/validators/RangeValidator.py\n@@ -1,13 +1,14 @@\n-from Products.validation.interfaces.IValidator import IValidator\n-from zope.interface import implementer\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n+from zope.interface import implementer\n+\n \n @implementer(IValidator)\n class RangeValidator:\n \n- def __init__(self, name, minval=0.0, maxval=0.0, title=\'\', description=\'\'):\n+ def __init__(self, name, minval=0.0, maxval=0.0, title="", description=""):\n self.name = name\n self.minval = minval\n self.maxval = maxval\n@@ -15,31 +16,35 @@ def __init__(self, name, minval=0.0, maxval=0.0, title=\'\', description=\'\'):\n self.description = description\n \n def __call__(self, value, *args, **kwargs):\n- if len(args)>=1:\n- minval=args[0]\n+ if len(args) >= 1:\n+ minval = args[0]\n else:\n- minval=self.minval\n+ minval = self.minval\n \n- if len(args)>=2:\n- maxval=args[1]\n+ if len(args) >= 2:\n+ maxval = args[1]\n else:\n- maxval=self.maxval\n+ maxval = self.maxval\n \n- assert(minval <= maxval)\n+ assert minval <= maxval\n try:\n nval = float(value)\n except ValueError:\n- msg = _(u"Validation failed($name): could not convert \'$value\' to number",\n- mapping = { \'name\' : safe_unicode(self.name), \'value\': safe_unicode(value)})\n+ msg = _(\n+ "Validation failed($name): could not convert \'$value\' to number",\n+ mapping={"name": safe_unicode(self.name), "value": safe_unicode(value)},\n+ )\n return recursiveTranslate(msg, **kwargs)\n if minval <= nval < maxval:\n return 1\n \n- msg = _(u"Validation failed($name): \'$value\' out of range($min, $max)",\n- mapping = {\n- \'name\' : safe_unicode(self.name),\n- \'value\': safe_unicode(value),\n- \'min\' : safe_unicode(minval),\n- \'max\' : safe_unicode(maxval),\n- })\n+ msg = _(\n+ "Validation failed($name): \'$value\' out of range($min, $max)",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ "min": safe_unicode(minval),\n+ "max": safe_unicode(maxval),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\ndiff --git a/Products/validation/validators/RegexValidator.py b/Products/validation/validators/RegexValidator.py\nindex 62923b3..beec9ee 100644\n--- a/Products/validation/validators/RegexValidator.py\n+++ b/Products/validation/validators/RegexValidator.py\n@@ -1,16 +1,15 @@\n-from Products.validation.interfaces.IValidator import IValidator\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n from zope.interface import implementer\n \n import re\n-import six\n \n \n def ignoreRE(value, expression):\n ignore = re.compile(expression)\n- return ignore.sub(\'\', value)\n+ return ignore.sub("", value)\n \n \n @implementer(IValidator)\n@@ -18,11 +17,11 @@ class RegexValidator:\n \n def __init__(self, name, *args, **kw):\n self.name = name\n- self.title = kw.get(\'title\', name)\n- self.description = kw.get(\'description\', \'\')\n- self.errmsg = kw.get(\'errmsg\', \'fails tests of %s\' % name)\n+ self.title = kw.get("title", name)\n+ self.description = kw.get("description", "")\n+ self.errmsg = kw.get("errmsg", "fails tests of %s" % name)\n self.regex_strings = args\n- self.ignore = kw.get(\'ignore\', None)\n+ self.ignore = kw.get("ignore", None)\n self.regex = []\n self.compileRegex()\n \n@@ -35,7 +34,7 @@ def __getstate__(self):\n I\'m using the getstate/setstate hooks to set self.regex to []\n """\n d = self.__dict__.copy()\n- d[\'regex\'] = []\n+ d["regex"] = []\n return d\n \n def __setstate__(self, dict):\n@@ -44,30 +43,33 @@ def __setstate__(self, dict):\n \n def __call__(self, value, *args, **kwargs):\n if not isinstance(value, str):\n- msg = _(u"Validation failed($name): $value of type $type, expected \'string\'",\n- mapping = {\n- \'name\' : safe_unicode(self.name),\n- \'value\': safe_unicode(value),\n- \'type\' : safe_unicode(type(value))\n- })\n+ msg = _(\n+ "Validation failed($name): $value of type $type, expected \'string\'",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ "type": safe_unicode(type(value)),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n \n- ignore = kwargs.get(\'ignore\', None)\n+ ignore = kwargs.get("ignore", None)\n if ignore:\n value = ignoreRE(value, ignore)\n elif self.ignore:\n value = ignoreRE(value, self.ignore)\n \n-\n for r in self.regex:\n m = r.match(value)\n if not m:\n- msg = _(u"Validation failed($name): \'$value\' $errmsg",\n- mapping={\n- \'name\' : safe_unicode(self.name),\n- \'value\': safe_unicode(value),\n- \'errmsg\' : safe_unicode(self.errmsg)\n- })\n+ msg = _(\n+ "Validation failed($name): \'$value\' $errmsg",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ "errmsg": safe_unicode(self.errmsg),\n+ },\n+ )\n \n return recursiveTranslate(msg, **kwargs)\n return 1\ndiff --git a/Products/validation/validators/SupplValidators.py b/Products/validation/validators/SupplValidators.py\nindex dce96d2..a68f4fe 100644\n--- a/Products/validation/validators/SupplValidators.py\n+++ b/Products/validation/validators/SupplValidators.py\n@@ -1,12 +1,13 @@\n from Acquisition import aq_base\n from DateTime import DateTime\n-from ZPublisher.HTTPRequest import FileUpload\n-\n-from Products.validation.interfaces.IValidator import IValidator\n-from zope.interface import implementer\n+from io import TextIOWrapper\n from Products.validation.i18n import PloneMessageFactory as _\n from Products.validation.i18n import recursiveTranslate\n from Products.validation.i18n import safe_unicode\n+from Products.validation.interfaces.IValidator import IValidator\n+from zope.interface import implementer\n+from ZPublisher.HTTPRequest import FileUpload\n+\n \n _marker = []\n \n@@ -25,22 +26,22 @@ class MaxSizeValidator:\n or a given maxsize at validator initialization.\n """\n \n- def __init__(self, name, title=\'\', description=\'\', maxsize=0):\n+ def __init__(self, name, title="", description="", maxsize=0):\n self.name = name\n self.title = title or name\n self.description = description\n- self.maxsize= maxsize\n+ self.maxsize = maxsize\n \n def __call__(self, value, *args, **kwargs):\n- instance = kwargs.get(\'instance\', None)\n- field = kwargs.get(\'field\', None)\n+ instance = kwargs.get("instance", None)\n+ field = kwargs.get("field", None)\n \n # get max size\n- if kwargs.has_key(\'maxsize\'):\n- maxsize = kwargs.get(\'maxsize\')\n- elif hasattr(aq_base(instance), \'getMaxSizeFor\'):\n+ if kwargs.has_key("maxsize"):\n+ maxsize = kwargs.get("maxsize")\n+ elif hasattr(aq_base(instance), "getMaxSizeFor"):\n maxsize = instance.getMaxSizeFor(field.getName())\n- elif hasattr(field, \'maxsize\'):\n+ elif hasattr(field, "maxsize"):\n maxsize = field.maxsize\n else:\n # set to given default value (default defaults to 0)\n@@ -50,9 +51,12 @@ def __call__(self, value, *args, **kwargs):\n return True\n \n # calculate size\n- elif (isinstance(value, FileUpload) or isinstance(value, file) or\n- hasattr(aq_base(value), \'tell\')):\n- value.seek(0, 2) # eof\n+ elif (\n+ isinstance(value, FileUpload)\n+ or isinstance(value, TextIOWrapper)\n+ or hasattr(aq_base(value), "tell")\n+ ):\n+ value.seek(0, 2) # eof\n size = value.tell()\n value.seek(0)\n else:\n@@ -61,15 +65,17 @@ def __call__(self, value, *args, **kwargs):\n except TypeError:\n size = 0\n size = float(size)\n- sizeMB = (size / (1024 * 1024))\n+ sizeMB = size / (1024 * 1024)\n \n if sizeMB > maxsize:\n- msg = _("Validation failed($name: Uploaded data is too large: ${size}MB (max ${max}MB)",\n- mapping = {\n- \'name\' : safe_unicode(self.name),\n- \'size\' : safe_unicode("%.3f" % sizeMB),\n- \'max\' : safe_unicode("%.3f" % maxsize)\n- })\n+ msg = _(\n+ "Validation failed($name: Uploaded data is too large: ${size}MB (max ${max}MB)",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "size": safe_unicode("%.3f" % sizeMB),\n+ "max": safe_unicode("%.3f" % maxsize),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n else:\n return True\n@@ -78,29 +84,36 @@ def __call__(self, value, *args, **kwargs):\n @implementer(IValidator)\n class DateValidator:\n \n- def __init__(self, name, title=\'\', description=\'\'):\n+ def __init__(self, name, title="", description=""):\n self.name = name\n self.title = title or name\n self.description = description\n \n def __call__(self, value, *args, **kwargs):\n if not value:\n- msg = _(u"Validation failed($name): value is empty ($value).",\n- mapping = {\'name\': self.name, \'value\': repr(value)})\n+ msg = _(\n+ "Validation failed($name): value is empty ($value).",\n+ mapping={"name": self.name, "value": repr(value)},\n+ )\n return recursiveTranslate(msg, **kwargs)\n if not isinstance(value, DateTime):\n try:\n value = DateTime(value)\n- except:\n- msg = _(u"Validation failed($name): could not convert $value to a date.",\n- mapping = {\'name\': safe_unicode(self.name), \'value\': safe_unicode(value)})\n+ except Exception:\n+ msg = _(\n+ "Validation failed($name): could not convert $value to a date.",\n+ mapping={\n+ "name": safe_unicode(self.name),\n+ "value": safe_unicode(value),\n+ },\n+ )\n return recursiveTranslate(msg, **kwargs)\n return True\n \n \n validatorList = [\n- MaxSizeValidator(\'isMaxSize\', title=\'\', description=\'\'),\n- DateValidator(\'isValidDate\', title=\'\', description=\'\'),\n- ]\n+ MaxSizeValidator("isMaxSize", title="", description=""),\n+ DateValidator("isValidDate", title="", description=""),\n+]\n \n-__all__ = (\'validatorList\', )\n+__all__ = ("validatorList",)\ndiff --git a/Products/validation/validators/__init__.py b/Products/validation/validators/__init__.py\nindex f6abbfa..181fe91 100644\n--- a/Products/validation/validators/__init__.py\n+++ b/Products/validation/validators/__init__.py\n@@ -1,20 +1,32 @@\n-from Products.validation.validators.RegexValidator import RegexValidator\n-from Products.validation.validators.RangeValidator import RangeValidator\n-from Products.validation.validators.ExpressionValidator import ExpressionValidator\n-\n-validators = []\n-\n+# This module gathers a list of validators, and registers them to the service.\n+#\n+# Three of these are backwards compatibility imports, marked with \'noqa F401\'\n+# for flake8. Of these, the RangeValidator and RegexValidator are included in\n+# baseValidators, so that is fine.\n+# ExpressionValidator is not included. This is because you need to create an\n+# instance of a validator class and register this. The ExpressionValidator\n+# class is fine as base for your custom validators, but no one thought of\n+# a default instance to add here.\n from Products.validation.validators.BaseValidators import baseValidators\n-validators.extend(baseValidators)\n+from Products.validation.validators.EmptyValidator import (\n+ validatorList as emptyValidators,\n+)\n+from Products.validation.validators.ExpressionValidator import ( # noqa F401\n+ ExpressionValidator,\n+)\n+from Products.validation.validators.IdValidator import validatorList as idValidators\n+from Products.validation.validators.RangeValidator import RangeValidator # noqa F401\n+from Products.validation.validators.RegexValidator import RegexValidator # noqa F401\n+from Products.validation.validators.SupplValidators import (\n+ validatorList as supplValidators,\n+)\n \n-from Products.validation.validators.EmptyValidator import validatorList\n-validators.extend(validatorList)\n \n-from Products.validation.validators.SupplValidators import validatorList\n-validators.extend(validatorList)\n-\n-from Products.validation.validators.IdValidator import validatorList\n-validators.extend(validatorList)\n+validators = []\n+validators.extend(baseValidators)\n+validators.extend(emptyValidators)\n+validators.extend(supplValidators)\n+validators.extend(idValidators)\n \n \n def initialize(service):\ndiff --git a/Products/validation/validators/validator.py b/Products/validation/validators/validator.py\ndeleted file mode 100644\nindex 5919568..0000000\n--- a/Products/validation/validators/validator.py\n+++ /dev/null\n@@ -1,3 +0,0 @@\n-# backward compatibility\n-from RegexValidator import RegexValidator\n-from RangeValidator import RangeValidator\ndiff --git a/dependabot.yml b/dependabot.yml\nnew file mode 100644\nindex 0000000..bbd3ab0\n--- /dev/null\n+++ b/dependabot.yml\n@@ -0,0 +1,11 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+version: 2\n+updates:\n+\n+ - package-ecosystem: "github-actions"\n+ directory: "/"\n+ schedule:\n+ # Check for updates to GitHub Actions every week\n+ interval: "weekly"\ndiff --git a/docs/ChangeLog b/docs/ChangeLog\nindex 2eb58a3..feee611 100644\n--- a/docs/ChangeLog\n+++ b/docs/ChangeLog\n@@ -38,7 +38,7 @@ DONT USE ChangeLog - USE history.txt instead!\n and description to validators.\n \n * service.py (Service.items): Improve the service a bit, to be\n- useable with the other registries.\n+ usable with the other registries.\n \n 2003-08-04 Sylvain Th\xe9nault \n \ndiff --git a/news/+meta.internal b/news/+meta.internal\nnew file mode 100644\nindex 0000000..c08f539\n--- /dev/null\n+++ b/news/+meta.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone devs]\ndiff --git a/news/60.breaking.1 b/news/60.breaking.1\nnew file mode 100644\nindex 0000000..1b0fd63\n--- /dev/null\n+++ b/news/60.breaking.1\n@@ -0,0 +1,5 @@\n+Drop support for Plone 5.2 and for Python 3.7 and lower.\n+Only Plone 6.0 and 6.1 are supported now.\n+Note that in Plone 6.1, this package is no longer tested together with the core.\n+It has been moved to the ecosystem versions.\n+[maurits]\ndiff --git a/news/60.breaking.2 b/news/60.breaking.2\nnew file mode 100644\nindex 0000000..e5c2cc5\n--- /dev/null\n+++ b/news/60.breaking.2\n@@ -0,0 +1,4 @@\n+Removed `Products/validation/validators/validator.py`.\n+This had backwards compatibility imports in a way that did not work on Python 3.\n+So apparently no one needed this so far.\n+[maurits]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..dc211d3 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -1,6 +1,12 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[build-system]\n+requires = ["setuptools>=68.2"]\n+\n [tool.towncrier]\n-filename = "CHANGES.rst"\n directory = "news/"\n+filename = "CHANGES.rst"\n title_format = "{version} ({project_date})"\n underlines = ["-", ""]\n \n@@ -18,3 +24,144 @@ showcontent = true\n directory = "bugfix"\n name = "Bug fixes:"\n showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "internal"\n+name = "Internal:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "documentation"\n+name = "Documentation:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "tests"\n+name = "Tests"\n+showcontent = true\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# towncrier_extra_lines = """\n+# extra_configuration\n+# """\n+##\n+\n+[tool.isort]\n+profile = "plone"\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# isort_extra_lines = """\n+# extra_configuration\n+# """\n+##\n+\n+[tool.black]\n+target-version = ["py38"]\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# black_extra_lines = """\n+# extra_configuration\n+# """\n+##\n+\n+[tool.codespell]\n+ignore-words-list = "discreet,assertin,"\n+skip = "*.po,"\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# codespell_ignores = "foo,bar"\n+# codespell_skip = "*.po,*.map,package-lock.json"\n+##\n+\n+[tool.dependencychecker]\n+Zope = [\n+ # Zope own provided namespaces\n+ \'App\', \'OFS\', \'Products.Five\', \'Products.OFSP\', \'Products.PageTemplates\',\n+ \'Products.SiteAccess\', \'Shared\', \'Testing\', \'ZPublisher\', \'ZTUtils\',\n+ \'Zope2\', \'webdav\', \'zmi\',\n+ # ExtensionClass own provided namespaces\n+ \'ExtensionClass\', \'ComputedAttribute\', \'MethodObject\',\n+ # Zope dependencies\n+ \'AccessControl\', \'Acquisition\', \'AuthEncoding\', \'beautifulsoup4\', \'BTrees\',\n+ \'cffi\', \'Chameleon\', \'DateTime\', \'DocumentTemplate\',\n+ \'MultiMapping\', \'multipart\', \'PasteDeploy\', \'Persistence\', \'persistent\',\n+ \'pycparser\', \'python-gettext\', \'pytz\', \'RestrictedPython\', \'roman\',\n+ \'soupsieve\', \'transaction\', \'waitress\', \'WebOb\', \'WebTest\', \'WSGIProxy2\',\n+ \'z3c.pt\', \'zc.lockfile\', \'ZConfig\', \'zExceptions\', \'ZODB\', \'zodbpickle\',\n+ \'zope.annotation\', \'zope.browser\', \'zope.browsermenu\', \'zope.browserpage\',\n+ \'zope.browserresource\', \'zope.cachedescriptors\', \'zope.component\',\n+ \'zope.configuration\', \'zope.container\', \'zope.contentprovider\',\n+ \'zope.contenttype\', \'zope.datetime\', \'zope.deferredimport\',\n+ \'zope.deprecation\', \'zope.dottedname\', \'zope.event\', \'zope.exceptions\',\n+ \'zope.filerepresentation\', \'zope.globalrequest\', \'zope.hookable\',\n+ \'zope.i18n\', \'zope.i18nmessageid\', \'zope.interface\', \'zope.lifecycleevent\',\n+ \'zope.location\', \'zope.pagetemplate\', \'zope.processlifetime\', \'zope.proxy\',\n+ \'zope.ptresource\', \'zope.publisher\', \'zope.schema\', \'zope.security\',\n+ \'zope.sequencesort\', \'zope.site\', \'zope.size\', \'zope.structuredtext\',\n+ \'zope.tal\', \'zope.tales\', \'zope.testbrowser\', \'zope.testing\',\n+ \'zope.traversing\', \'zope.viewlet\'\n+]\n+\'Products.CMFCore\' = [\n+ \'docutils\', \'five.localsitemanager\', \'Missing\', \'Products.BTreeFolder2\',\n+ \'Products.GenericSetup\', \'Products.MailHost\', \'Products.PythonScripts\',\n+ \'Products.StandardCacheManagers\', \'Products.ZCatalog\', \'Record\',\n+ \'zope.sendmail\', \'Zope\'\n+]\n+\'plone.base\' = [\n+ \'plone.batching\', \'plone.registry\', \'plone.schema\',\'plone.z3cform\',\n+ \'Products.CMFCore\', \'Products.CMFDynamicViewFTI\',\n+]\n+python-dateutil = [\'dateutil\']\n+ignore-packages = [\'Products.CMFPlone\']\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# dependencies_ignores = "[\'zestreleaser.towncrier\']"\n+# dependencies_mappings = [\n+# "gitpython = [\'git\']",\n+# "pygithub = [\'github\']",\n+# ]\n+##\n+\n+[tool.check-manifest]\n+ignore = [\n+ ".editorconfig",\n+ ".flake8",\n+ ".meta.toml",\n+ ".pre-commit-config.yaml",\n+ "dependabot.yml",\n+ "mx.ini",\n+ "tox.ini",\n+\n+]\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# check_manifest_ignores = """\n+# "*.map.js",\n+# "*.pyc",\n+# """\n+# check_manifest_extra_lines = """\n+# ignore-bad-ideas = [\n+# "some/test/file/PKG-INFO",\n+# ]\n+# """\n+##\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [pyproject]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\ndiff --git a/setup.cfg b/setup.cfg\ndeleted file mode 100644\nindex 2a9acf1..0000000\n--- a/setup.cfg\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-[bdist_wheel]\n-universal = 1\ndiff --git a/setup.py b/setup.py\nindex 802d690..e1bfc0e 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -1,48 +1,43 @@\n-from setuptools import setup, find_packages\n+from setuptools import find_packages\n+from setuptools import setup\n \n-version = \'2.1.4.dev0\'\n+\n+version = "3.0.0.dev0"\n \n setup(\n- name=\'Products.validation\',\n+ name="Products.validation",\n version=version,\n description="Data validation package for Zope",\n- long_description=(open("README.rst").read() + "\\n" +\n- open("CHANGES.rst").read()),\n+ long_description=(open("README.rst").read() + "\\n" + open("CHANGES.rst").read()),\n classifiers=[\n "Development Status :: 6 - Mature",\n- "Framework :: Zope2",\n- "Framework :: Zope :: 4",\n+ "Framework :: Zope",\n "Framework :: Zope :: 5",\n "Operating System :: OS Independent",\n "Framework :: Plone",\n- "Framework :: Plone :: 5.1",\n- "Framework :: Plone :: 5.2",\n "Framework :: Plone :: 6.0",\n+ "Framework :: Plone :: 6.1",\n "License :: OSI Approved :: GNU General Public License (GPL)",\n "Programming Language :: Python",\n- "Programming Language :: Python :: 2.7",\n- "Programming Language :: Python :: 3.6",\n- "Programming Language :: Python :: 3.7",\n "Programming Language :: Python :: 3.8",\n+ "Programming Language :: Python :: 3.9",\n+ "Programming Language :: Python :: 3.10",\n+ "Programming Language :: Python :: 3.11",\n+ "Programming Language :: Python :: 3.12",\n ],\n- keywords=\'Zope validation regex email\',\n- author=\'Benjamin Saller\',\n- author_email=\'plone-developers@lists.sourceforge.net\',\n- url=\'https://pypi.org/project/Products.validation\',\n- license=\'GPL\',\n+ keywords="Zope validation regex email",\n+ author="Benjamin Saller",\n+ author_email="plone-developers@lists.sourceforge.net",\n+ url="https://github.com/plone/Products.validation",\n+ license="GPL",\n packages=find_packages(),\n- namespace_packages=[\'Products\'],\n+ namespace_packages=["Products"],\n include_package_data=True,\n zip_safe=False,\n extras_require={},\n+ python_requires=">=3.8",\n install_requires=[\n- \'setuptools\',\n- \'six\',\n- \'zope.i18n\',\n- \'zope.i18nmessageid\',\n- \'zope.interface\',\n- \'Acquisition\',\n- \'DateTime\',\n- \'Zope2\',\n+ "setuptools",\n+ "Zope",\n ],\n )\ndiff --git a/tox.ini b/tox.ini\nnew file mode 100644\nindex 0000000..d9467e8\n--- /dev/null\n+++ b/tox.ini\n@@ -0,0 +1,220 @@\n+# Generated from:\n+# https://github.com/plone/meta/tree/main/config/default\n+# See the inline comments on how to expand/tweak this configuration file\n+[tox]\n+# We need 4.4.0 for constrain_package_deps.\n+min_version = 4.4.0\n+envlist =\n+ lint\n+ test\n+ dependencies\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# envlist_lines = """\n+# my_other_environment\n+# """\n+# config_lines = """\n+# my_extra_top_level_tox_configuration_lines\n+# """\n+##\n+\n+[testenv]\n+skip_install = true\n+allowlist_externals =\n+ echo\n+ false\n+# Make sure typos like `tox -e formaat` are caught instead of silently doing nothing.\n+# See https://github.com/tox-dev/tox/issues/2858.\n+commands =\n+ echo "Unrecognized environment name {envname}"\n+ false\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# testenv_options = """\n+# basepython = /usr/bin/python3.8\n+# """\n+##\n+\n+[testenv:init]\n+description = Prepare environment\n+skip_install = true\n+commands =\n+ echo "Initial setup complete"\n+\n+\n+[testenv:format]\n+description = automatically reformat code\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a pyupgrade\n+ pre-commit run -a isort\n+ pre-commit run -a black\n+ pre-commit run -a zpretty\n+\n+[testenv:lint]\n+description = run linters that will help improve the code style\n+skip_install = true\n+deps =\n+ pre-commit\n+commands =\n+ pre-commit run -a\n+\n+[testenv:dependencies]\n+description = check if the package defines all its dependencies\n+skip_install = true\n+deps =\n+ build\n+ z3c.dependencychecker==2.14.3\n+commands =\n+ python -m build --sdist\n+ dependencychecker\n+\n+[testenv:dependencies-graph]\n+description = generate a graph out of the dependencies of the package\n+skip_install = false\n+allowlist_externals =\n+ sh\n+deps =\n+ pipdeptree==2.5.1\n+ graphviz # optional dependency of pipdeptree\n+commands =\n+ sh -c \'pipdeptree --exclude setuptools,wheel,pipdeptree,zope.interface,zope.component --graph-output svg > dependencies.svg\'\n+\n+[testenv:test]\n+description = run the distribution tests\n+use_develop = true\n+skip_install = false\n+constrain_package_deps = true\n+set_env =\n+ ROBOT_BROWSER=headlesschrome\n+\n+##\n+# Specify extra test environment variables in .meta.toml:\n+# [tox]\n+# test_environment_variables = """\n+# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/\n+# """\n+#\n+# Set constrain_package_deps .meta.toml:\n+# [tox]\n+# constrain_package_deps = "false"\n+##\n+deps =\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+##\n+# Specify additional deps in .meta.toml:\n+# [tox]\n+# test_deps_additional = "-esources/plonegovbr.portal_base[test]"\n+#\n+# Specify a custom constraints file in .meta.toml:\n+# [tox]\n+# constraints_file = "https://my-server.com/constraints.txt"\n+##\n+commands =\n+ zope-testrunner --all --test-path={toxinidir} -s Products.validation {posargs}\n+extras =\n+ test\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# test_extras = """\n+# tests\n+# widgets\n+# """\n+##\n+\n+[testenv:coverage]\n+description = get a test coverage report\n+use_develop = true\n+skip_install = false\n+constrain_package_deps = true\n+set_env =\n+ ROBOT_BROWSER=headlesschrome\n+\n+##\n+# Specify extra test environment variables in .meta.toml:\n+# [tox]\n+# test_environment_variables = """\n+# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/\n+# """\n+##\n+deps =\n+ coverage\n+ zope.testrunner\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+commands =\n+ coverage run --branch --source Products.validation {envbindir}/zope-testrunner --quiet --all --test-path={toxinidir} -s Products.validation {posargs}\n+ coverage report -m --format markdown\n+ coverage xml\n+ coverage html\n+extras =\n+ test\n+\n+\n+[testenv:release-check]\n+description = ensure that the distribution is ready to release\n+skip_install = true\n+deps =\n+ twine\n+ build\n+ towncrier\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+commands =\n+ # fake version to not have to install the package\n+ # we build the change log as news entries might break\n+ # the README that is displayed on PyPI\n+ towncrier build --version=100.0.0 --yes\n+ python -m build --sdist\n+ twine check dist/*\n+\n+[testenv:circular]\n+description = ensure there are no cyclic dependencies\n+use_develop = true\n+skip_install = false\n+# Here we must always constrain the package deps to what is already installed,\n+# otherwise we simply get the latest from PyPI, which may not work.\n+constrain_package_deps = true\n+set_env =\n+\n+##\n+# Specify extra test environment variables in .meta.toml:\n+# [tox]\n+# test_environment_variables = """\n+# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/\n+# """\n+##\n+allowlist_externals =\n+ sh\n+deps =\n+ pipdeptree\n+ pipforester\n+ -c https://dist.plone.org/release/6.0-dev/constraints.txt\n+\n+commands =\n+ # Generate the full dependency tree\n+ sh -c \'pipdeptree -j > forest.json\'\n+ # Generate a DOT graph with the circular dependencies, if any\n+ pipforester -i forest.json -o forest.dot --cycles\n+ # Report if there are any circular dependencies, i.e. error if there are any\n+ pipforester -i forest.json --check-cycles -o /dev/null\n+\n+\n+##\n+# Add extra configuration options in .meta.toml:\n+# [tox]\n+# extra_lines = """\n+# _your own configuration lines_\n+# """\n+##\n' -