From 079247c9e99f85c29389d11ad4e7ef3f1060735d Mon Sep 17 00:00:00 2001 From: davisagli Date: Mon, 2 Oct 2023 20:35:45 +0200 Subject: [PATCH] [fc] Repository: plone.restapi Branch: refs/heads/main Date: 2023-10-02T20:35:45+02:00 Author: Steve Piercy (stevepiercy) Commit: https://github.com/plone/plone.restapi/commit/3927a8978b7242189c74d15668dd809674a2c5ea Fix redirect for json-schema.org (#1718) * Fix redirect * Add changelog Files changed: A news/1718.documentation M docs/source/endpoints/types.md --- last_commit.txt | 55 +++++++++---------------------------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/last_commit.txt b/last_commit.txt index 3156083041..bbcd2af9a9 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,55 +1,20 @@ -Repository: plone.rest +Repository: plone.restapi Branch: refs/heads/main -Date: 2023-10-01T20:04:11+02:00 -Author: Timo Stollenwerk (tisto) -Commit: https://github.com/plone/plone.rest/commit/79f1d19f00582fbf49af8ca45533fa065de1ddd4 +Date: 2023-10-02T20:35:45+02:00 +Author: Steve Piercy (stevepiercy) +Commit: https://github.com/plone/plone.restapi/commit/3927a8978b7242189c74d15668dd809674a2c5ea -Pre commit tisto (#161) +Fix redirect for json-schema.org (#1718) -* Run pre-commit.ci, fix three typos +* Fix redirect -* Use same black version as pre-commit.ci - -* Set black version to pre-commit.ci one - -* black - -* Add isort/black config to pyproject.toml +* Add changelog Files changed: -M CHANGES.rst -M Makefile -M README.rst -M plone-5.2.x.cfg -M plone-6.0.x.cfg -M pyproject.toml -M setup.py -M src/plone/rest/__init__.py -M src/plone/rest/cors.py -M src/plone/rest/demo.py -M src/plone/rest/errors.py -M src/plone/rest/events.py -M src/plone/rest/interfaces.py -M src/plone/rest/negotiation.py -M src/plone/rest/patches.py -M src/plone/rest/service.py -M src/plone/rest/testing.py -M src/plone/rest/tests/__init__.py -M src/plone/rest/tests/test_cors.py -M src/plone/rest/tests/test_dexterity.py -M src/plone/rest/tests/test_dispatching.py -M src/plone/rest/tests/test_error_handling.py -M src/plone/rest/tests/test_named_services.py -M src/plone/rest/tests/test_negotiation.py -M src/plone/rest/tests/test_permissions.py -M src/plone/rest/tests/test_redirects.py -M src/plone/rest/tests/test_siteroot.py -M src/plone/rest/tests/test_traversal.py -M src/plone/rest/traverse.py -M src/plone/rest/zcml.py -M versions.cfg +A news/1718.documentation +M docs/source/endpoints/types.md -b'diff --git a/CHANGES.rst b/CHANGES.rst\nindex fc4e18a..dbb7900 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -245,7 +245,7 @@ Bugfixes:\n [buchi]\n \n - Fallback to regular views during traversal to ensure compatibility with\n- views beeing called with a specific Accept header.\n+ views being called with a specific Accept header.\n [buchi]\n \n \n@@ -295,7 +295,7 @@ Bugfixes:\n \n - Refactor traversal of REST requests by using a traversal adapter on the site\n root instead of a traversal adapter for each REST service. This prevents\n- REST services from being overriden by other traversal adapters.\n+ REST services from being overridden by other traversal adapters.\n [buchi]\n \n \ndiff --git a/Makefile b/Makefile\nindex 0a28fd4..cffb994 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -87,6 +87,7 @@ build-plone-6.0: ## Build Plone 6.0\n \tpython$(version) -m venv .\n \tbin/pip install --upgrade pip\n \tbin/pip install -r requirements-6.0.x.txt\n+\tbin/pip install pip install black==$$(awk \'/^black =/{print $$NF}\' versions.cfg)\n \tbin/buildout -c plone-6.0.x.cfg\n \n .PHONY: Test\ndiff --git a/README.rst b/README.rst\nindex 86e24f7..6c3a78d 100644\n--- a/README.rst\n+++ b/README.rst\n@@ -271,7 +271,7 @@ allow_origin\n allow_methods\n A comma separated list of HTTP method names that are allowed by this CORS\n policy, e.g. "DELETE,GET,OPTIONS,PATCH,POST,PUT". If not specified, all\n- methods for which there\'s a service registerd are allowed.\n+ methods for which there\'s a service registered are allowed.\n \n allow_credentials\n Indicates whether the resource supports user credentials in the request.\ndiff --git a/plone-5.2.x.cfg b/plone-5.2.x.cfg\nindex 82f35ea..3b37a17 100644\n--- a/plone-5.2.x.cfg\n+++ b/plone-5.2.x.cfg\n@@ -7,7 +7,7 @@ versions=versions\n \n [versions]\n plone.rest =\n-black = 21.12b0\n+black = 23.3.0\n \n # Error: The requirement (\'virtualenv>=20.0.35\') is not allowed by your [versions] constraint (20.0.26)\n virtualenv = 20.0.35\ndiff --git a/plone-6.0.x.cfg b/plone-6.0.x.cfg\nindex 746358a..f1fead3 100644\n--- a/plone-6.0.x.cfg\n+++ b/plone-6.0.x.cfg\n@@ -9,5 +9,5 @@ zodb-temporary-storage = off\n \n [versions]\n plone.rest =\n-black = 21.7b0\n+black = 23.3.0\n pygments = 2.14.0\n\\ No newline at end of file\ndiff --git a/pyproject.toml b/pyproject.toml\nindex da067fa..a9ead58 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -22,4 +22,10 @@ showcontent = true\n [[tool.towncrier.type]]\n directory = "internal"\n name = "Internal:"\n-showcontent = true\n\\ No newline at end of file\n+showcontent = true\n+\n+[tool.isort]\n+profile = "plone"\n+\n+[tool.black]\n+target-version = ["py38"]\n\\ No newline at end of file\ndiff --git a/setup.py b/setup.py\nindex 1562c14..7b8c67c 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -1,6 +1,7 @@\n-import os\n+from setuptools import find_packages\n+from setuptools import setup\n \n-from setuptools import find_packages, setup\n+import os\n \n \n def read(*rnames):\ndiff --git a/src/plone/rest/__init__.py b/src/plone/rest/__init__.py\nindex 44646e4..ab37f22 100644\n--- a/src/plone/rest/__init__.py\n+++ b/src/plone/rest/__init__.py\n@@ -1,2 +1 @@\n-# -*- coding: utf-8 -*-\n from plone.rest.service import Service # noqa\ndiff --git a/src/plone/rest/cors.py b/src/plone/rest/cors.py\nindex dbc2035..737bed8 100644\n--- a/src/plone/rest/cors.py\n+++ b/src/plone/rest/cors.py\n@@ -1,7 +1,7 @@\n-# -*- coding: utf-8 -*-\n from plone.rest.interfaces import ICORSPolicy\n from zope.interface import implementer\n \n+\n # CORS preflight service registry\n # A mapping of method -> service_id\n _services = {}\n@@ -19,7 +19,7 @@ def lookup_preflight_service_id(method):\n \n \n @implementer(ICORSPolicy)\n-class CORSPolicy(object):\n+class CORSPolicy:\n def __init__(self, context, request):\n self.context = context\n self.request = request\ndiff --git a/src/plone/rest/demo.py b/src/plone/rest/demo.py\nindex 4622f97..4a3bcf5 100644\n--- a/src/plone/rest/demo.py\n+++ b/src/plone/rest/demo.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.rest import Service\n \n import json\ndiff --git a/src/plone/rest/errors.py b/src/plone/rest/errors.py\nindex 454d48e..d6a9c53 100644\n--- a/src/plone/rest/errors.py\n+++ b/src/plone/rest/errors.py\n@@ -1,5 +1,6 @@\n from AccessControl import getSecurityManager\n \n+\n try:\n from plone.app.redirector.interfaces import IRedirectionStorage\n except ImportError:\n@@ -10,10 +11,11 @@\n from Products.CMFCore.permissions import ManagePortal\n from Products.Five.browser import BrowserView\n from six.moves import urllib\n-from six.moves.urllib.parse import quote\n-from six.moves.urllib.parse import unquote\n+from urllib.parse import quote\n+from urllib.parse import unquote\n from zExceptions import NotFound\n \n+\n try:\n from ZPublisher.HTTPRequest import WSGIRequest\n \n@@ -61,7 +63,7 @@ def render_exception(self, exception):\n if six.PY2:\n name = name.decode("utf-8")\n message = message.decode("utf-8")\n- result = {u"type": name, u"message": message}\n+ result = {"type": name, "message": message}\n \n policy = queryMultiAdapter((self.context, self.request), ICORSPolicy)\n if policy is not None:\n@@ -77,10 +79,10 @@ def render_exception(self, exception):\n # NotFound exceptions need special handling because their\n # exception message gets turned into HTML by ZPublisher\n url = self.request.getURL()\n- result[u"message"] = u"Resource not found: %s" % url\n+ result["message"] = "Resource not found: %s" % url\n \n if getSecurityManager().checkPermission(ManagePortal, getSite()):\n- result[u"traceback"] = self.render_traceback(exception)\n+ result["traceback"] = self.render_traceback(exception)\n \n return result\n \n@@ -101,8 +103,8 @@ def render_traceback(self, exception):\n pass\n else:\n return (\n- u"ERROR: Another exception happened before we could "\n- u"render the traceback."\n+ "ERROR: Another exception happened before we could "\n+ "render the traceback."\n )\n \n raw = "\\n".join(traceback.format_tb(exc_traceback))\n@@ -192,7 +194,7 @@ def attempt_redirect(self):\n \n query_string = self.request.QUERY_STRING\n if query_string:\n- new_path = storage.get("%s?%s" % (old_path, query_string))\n+ new_path = storage.get(f"{old_path}?{query_string}")\n # if we matched on the query_string we don\'t want to include it\n # in redirect\n if new_path:\ndiff --git a/src/plone/rest/events.py b/src/plone/rest/events.py\nindex 49554d8..536ea64 100644\n--- a/src/plone/rest/events.py\n+++ b/src/plone/rest/events.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.rest.cors import lookup_preflight_service_id\n from plone.rest.interfaces import IAPIRequest\n from plone.rest.negotiation import lookup_service_id\ndiff --git a/src/plone/rest/interfaces.py b/src/plone/rest/interfaces.py\nindex ac18224..bf07258 100644\n--- a/src/plone/rest/interfaces.py\n+++ b/src/plone/rest/interfaces.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from zope.interface import Interface\n \n \ndiff --git a/src/plone/rest/negotiation.py b/src/plone/rest/negotiation.py\nindex 7a47d5a..63f2f31 100644\n--- a/src/plone/rest/negotiation.py\n+++ b/src/plone/rest/negotiation.py\n@@ -1,5 +1,3 @@\n-# -*- coding: utf-8 -*-\n-\n # Service registry\n # A mapping of method -> type name -> subtype name -> service id\n _services = {}\n@@ -42,7 +40,7 @@ def register_service(method, media_type):\n """Register a service for the given request method and media type and\n return it\'s service id.\n """\n- service_id = u"{}_{}_{}_".format(method, media_type[0], media_type[1])\n+ service_id = f"{method}_{media_type[0]}_{media_type[1]}_"\n types = _services.setdefault(method, {})\n subtypes = types.setdefault(media_type[0], {})\n subtypes[media_type[1]] = service_id\ndiff --git a/src/plone/rest/patches.py b/src/plone/rest/patches.py\nindex 97b618f..8f4cb5d 100644\n--- a/src/plone/rest/patches.py\n+++ b/src/plone/rest/patches.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.rest.interfaces import IAPIRequest\n \n \ndiff --git a/src/plone/rest/service.py b/src/plone/rest/service.py\nindex 351b111..7761f8f 100644\n--- a/src/plone/rest/service.py\n+++ b/src/plone/rest/service.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.rest.interfaces import ICORSPolicy\n from plone.rest.interfaces import IService\n from zope.component import queryMultiAdapter\n@@ -6,7 +5,7 @@\n \n \n @implementer(IService)\n-class Service(object):\n+class Service:\n def __call__(self):\n policy = queryMultiAdapter((self.context, self.request), ICORSPolicy)\n if policy is not None:\n@@ -29,4 +28,4 @@ def __getattribute__(self, name):\n # include credentials\n if name == "__roles__" and self.request._rest_cors_preflight:\n return ["Anonymous"]\n- return super(Service, self).__getattribute__(name)\n+ return super().__getattribute__(name)\ndiff --git a/src/plone/rest/testing.py b/src/plone/rest/testing.py\nindex 05268d9..bcd4eeb 100644\n--- a/src/plone/rest/testing.py\n+++ b/src/plone/rest/testing.py\n@@ -1,16 +1,13 @@\n-# -*- coding: utf-8 -*-\n from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE\n from plone.app.testing import FunctionalTesting\n from plone.app.testing import IntegrationTesting\n from plone.app.testing import PloneSandboxLayer\n from plone.rest.service import Service\n from plone.testing import z2\n-\n from zope.configuration import xmlconfig\n \n \n class PloneRestLayer(PloneSandboxLayer):\n-\n defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,)\n \n def setUpZope(self, app, configurationContext):\n@@ -31,7 +28,7 @@ def setUpZope(self, app, configurationContext):\n \n class InternalServerErrorService(Service):\n def __call__(self):\n- from six.moves.urllib.error import HTTPError\n+ from urllib.error import HTTPError\n \n raise HTTPError(\n "http://nohost/plone/500-internal-server-error",\ndiff --git a/src/plone/rest/tests/__init__.py b/src/plone/rest/tests/__init__.py\nindex 40a96af..e69de29 100644\n--- a/src/plone/rest/tests/__init__.py\n+++ b/src/plone/rest/tests/__init__.py\n@@ -1 +0,0 @@\n-# -*- coding: utf-8 -*-\ndiff --git a/src/plone/rest/tests/test_cors.py b/src/plone/rest/tests/test_cors.py\nindex 964ea1d..ddf1f1a 100644\n--- a/src/plone/rest/tests/test_cors.py\n+++ b/src/plone/rest/tests/test_cors.py\n@@ -1,5 +1,3 @@\n-# -*- coding: utf-8 -*-\n-from ZPublisher.pubevents import PubStart\n from plone.app.testing import popGlobalRegistry\n from plone.app.testing import pushGlobalRegistry\n from plone.rest.cors import CORSPolicy\n@@ -10,12 +8,12 @@\n from zope.event import notify\n from zope.interface import Interface\n from zope.publisher.interfaces.browser import IDefaultBrowserLayer\n+from ZPublisher.pubevents import PubStart\n \n import unittest\n \n \n class TestCORSPolicy(unittest.TestCase):\n-\n layer = PLONE_REST_INTEGRATION_TESTING\n \n def setUp(self):\n@@ -195,7 +193,6 @@ def test_preflight_cors_sets_status_code_200(self):\n \n \n class TestCORS(unittest.TestCase):\n-\n layer = PLONE_REST_INTEGRATION_TESTING\n \n def setUp(self):\n@@ -231,7 +228,7 @@ def test_simple_cors_gets_processed(self):\n \n def test_preflight_request_without_cors_policy_doesnt_render_service(self):\n # "Unregister" the current CORS policy\n- class NoCORSPolicy(object):\n+ class NoCORSPolicy:\n def __new__(cls, context, request):\n return None\n \ndiff --git a/src/plone/rest/tests/test_dexterity.py b/src/plone/rest/tests/test_dexterity.py\nindex 288ae48..cc41102 100644\n--- a/src/plone/rest/tests/test_dexterity.py\n+++ b/src/plone/rest/tests/test_dexterity.py\n@@ -1,25 +1,23 @@\n-# -*- coding: utf-8 -*-\n from datetime import datetime\n from plone.app.testing import setRoles\n-from plone.app.testing import TEST_USER_ID\n from plone.app.testing import SITE_OWNER_NAME\n from plone.app.testing import SITE_OWNER_PASSWORD\n+from plone.app.testing import TEST_USER_ID\n from plone.app.textfield.value import RichTextValue\n-from plone.namedfile.file import NamedBlobImage\n from plone.namedfile.file import NamedBlobFile\n+from plone.namedfile.file import NamedBlobImage\n from plone.rest.testing import PLONE_REST_FUNCTIONAL_TESTING\n from z3c.relationfield import RelationValue\n from zope.component import getUtility\n from zope.intid.interfaces import IIntIds\n \n-import unittest\n import os\n import requests\n import transaction\n+import unittest\n \n \n class TestDexterityServiceEndpoints(unittest.TestCase):\n-\n layer = PLONE_REST_FUNCTIONAL_TESTING\n \n def setUp(self):\n@@ -39,8 +37,8 @@ def test_dexterity_document_get(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"doc1", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("doc1", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_dexterity_document_post(self):\n response = requests.post(\n@@ -49,8 +47,8 @@ def test_dexterity_document_post(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"doc1", response.json().get("id"))\n- self.assertEqual(u"POST", response.json().get("method"))\n+ self.assertEqual("doc1", response.json().get("id"))\n+ self.assertEqual("POST", response.json().get("method"))\n \n def test_dexterity_document_put(self):\n response = requests.put(\n@@ -59,8 +57,8 @@ def test_dexterity_document_put(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"doc1", response.json().get("id"))\n- self.assertEqual(u"PUT", response.json().get("method"))\n+ self.assertEqual("doc1", response.json().get("id"))\n+ self.assertEqual("PUT", response.json().get("method"))\n \n def test_dexterity_document_patch(self):\n response = requests.patch(\n@@ -69,8 +67,8 @@ def test_dexterity_document_patch(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"doc1", response.json().get("id"))\n- self.assertEqual(u"PATCH", response.json().get("method"))\n+ self.assertEqual("doc1", response.json().get("id"))\n+ self.assertEqual("PATCH", response.json().get("method"))\n \n def test_dexterity_document_delete(self):\n response = requests.delete(\n@@ -79,8 +77,8 @@ def test_dexterity_document_delete(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"doc1", response.json().get("id"))\n- self.assertEqual(u"DELETE", response.json().get("method"))\n+ self.assertEqual("doc1", response.json().get("id"))\n+ self.assertEqual("DELETE", response.json().get("method"))\n \n def test_dexterity_document_options(self):\n response = requests.options(\n@@ -89,8 +87,8 @@ def test_dexterity_document_options(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"doc1", response.json().get("id"))\n- self.assertEqual(u"OPTIONS", response.json().get("method"))\n+ self.assertEqual("doc1", response.json().get("id"))\n+ self.assertEqual("OPTIONS", response.json().get("method"))\n \n def test_dexterity_folder_get(self):\n self.portal.invokeFactory("Folder", id="folder")\n@@ -103,23 +101,23 @@ def test_dexterity_folder_get(self):\n )\n \n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"folder", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("folder", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_dexterity_news_item_get(self):\n self.portal.invokeFactory("News Item", id="newsitem")\n self.portal.newsitem.title = "My News Item"\n- self.portal.newsitem.description = u"This is a news item"\n+ self.portal.newsitem.description = "This is a news item"\n self.portal.newsitem.text = RichTextValue(\n- u"Lorem ipsum", "text/plain", "text/html"\n+ "Lorem ipsum", "text/plain", "text/html"\n )\n- image_file = os.path.join(os.path.dirname(__file__), u"image.png")\n+ image_file = os.path.join(os.path.dirname(__file__), "image.png")\n fd = open(image_file, "rb")\n self.portal.newsitem.image = NamedBlobImage(\n- data=fd.read(), contentType="image/png", filename=u"image.png"\n+ data=fd.read(), contentType="image/png", filename="image.png"\n )\n fd.close()\n- self.portal.newsitem.image_caption = u"This is an image caption."\n+ self.portal.newsitem.image_caption = "This is an image caption."\n import transaction\n \n transaction.commit()\n@@ -130,13 +128,13 @@ def test_dexterity_news_item_get(self):\n )\n \n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"newsitem", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("newsitem", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_dexterity_event_get(self):\n self.portal.invokeFactory("Event", id="event")\n self.portal.event.title = "Event"\n- self.portal.event.description = u"This is an event"\n+ self.portal.event.description = "This is an event"\n self.portal.event.start = datetime(2013, 1, 1, 10, 0)\n self.portal.event.end = datetime(2013, 1, 1, 12, 0)\n import transaction\n@@ -149,13 +147,13 @@ def test_dexterity_event_get(self):\n )\n \n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"event", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("event", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_dexterity_link_get(self):\n self.portal.invokeFactory("Link", id="link")\n self.portal.link.title = "My Link"\n- self.portal.link.description = u"This is a link"\n+ self.portal.link.description = "This is a link"\n self.portal.remoteUrl = "http://plone.org"\n import transaction\n \n@@ -167,17 +165,17 @@ def test_dexterity_link_get(self):\n )\n \n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"link", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("link", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_dexterity_file_get(self):\n self.portal.invokeFactory("File", id="file")\n self.portal.file.title = "My File"\n- self.portal.file.description = u"This is a file"\n- pdf_file = os.path.join(os.path.dirname(__file__), u"file.pdf")\n+ self.portal.file.description = "This is a file"\n+ pdf_file = os.path.join(os.path.dirname(__file__), "file.pdf")\n fd = open(pdf_file, "rb")\n self.portal.file.file = NamedBlobFile(\n- data=fd.read(), contentType="application/pdf", filename=u"file.pdf"\n+ data=fd.read(), contentType="application/pdf", filename="file.pdf"\n )\n fd.close()\n intids = getUtility(IIntIds)\n@@ -194,17 +192,17 @@ def test_dexterity_file_get(self):\n )\n \n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"file", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("file", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_dexterity_image_get(self):\n self.portal.invokeFactory("Image", id="image")\n self.portal.image.title = "My Image"\n- self.portal.image.description = u"This is an image"\n- image_file = os.path.join(os.path.dirname(__file__), u"image.png")\n+ self.portal.image.description = "This is an image"\n+ image_file = os.path.join(os.path.dirname(__file__), "image.png")\n fd = open(image_file, "rb")\n self.portal.image.image = NamedBlobImage(\n- data=fd.read(), contentType="image/png", filename=u"image.png"\n+ data=fd.read(), contentType="image/png", filename="image.png"\n )\n fd.close()\n import transaction\n@@ -218,13 +216,13 @@ def test_dexterity_image_get(self):\n )\n \n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"image", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("image", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_dexterity_collection_get(self):\n self.portal.invokeFactory("Collection", id="collection")\n self.portal.collection.title = "My Collection"\n- self.portal.collection.description = u"This is a collection with two documents"\n+ self.portal.collection.description = "This is a collection with two documents"\n self.portal.collection.query = [\n {\n "i": "portal_type",\n@@ -243,5 +241,5 @@ def test_dexterity_collection_get(self):\n )\n \n self.assertEqual(200, response.status_code)\n- self.assertEqual(u"collection", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("collection", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\ndiff --git a/src/plone/rest/tests/test_dispatching.py b/src/plone/rest/tests/test_dispatching.py\nindex fb9b7b8..5c9204d 100644\n--- a/src/plone/rest/tests/test_dispatching.py\n+++ b/src/plone/rest/tests/test_dispatching.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.app.testing import setRoles\n from plone.app.testing import SITE_OWNER_NAME\n from plone.app.testing import SITE_OWNER_PASSWORD\n@@ -17,7 +16,6 @@\n \n \n class DispatchingTestCase(unittest.TestCase):\n-\n layer = PLONE_REST_FUNCTIONAL_TESTING\n \n def setUp(self):\n@@ -54,7 +52,7 @@ def validate(self, expectations, follow_redirects=False):\n \n if failures:\n msg = ""\n- for (request_args, expected_status, actual_status) in failures:\n+ for request_args, expected_status, actual_status in failures:\n msg += (\n "\\n"\n "Request: %s\\n"\n@@ -137,7 +135,7 @@ def test_not_found_invalid_creds(self):\n \n class TestDispatchingDexterity(DispatchingTestCase):\n def setUp(self):\n- super(TestDispatchingDexterity, self).setUp()\n+ super().setUp()\n self.portal.invokeFactory("Folder", id="private")\n \n self.portal.invokeFactory("Folder", id="public")\n@@ -216,7 +214,7 @@ def test_public_dx_folder_invalid_creds(self):\n \n class TestDispatchingRedirects(DispatchingTestCase):\n def setUp(self):\n- super(TestDispatchingRedirects, self).setUp()\n+ super().setUp()\n \n self.portal.invokeFactory("Folder", id="private-old")\n self.portal.manage_renameObject("private-old", "private-new")\ndiff --git a/src/plone/rest/tests/test_error_handling.py b/src/plone/rest/tests/test_error_handling.py\nindex b2cf511..32711a4 100644\n--- a/src/plone/rest/tests/test_error_handling.py\n+++ b/src/plone/rest/tests/test_error_handling.py\n@@ -1,9 +1,8 @@\n-# -*- coding: utf-8 -*-\n from plone.app.testing import setRoles\n-from plone.app.testing import TEST_USER_ID\n-from plone.app.testing import TEST_USER_PASSWORD\n from plone.app.testing import SITE_OWNER_NAME\n from plone.app.testing import SITE_OWNER_PASSWORD\n+from plone.app.testing import TEST_USER_ID\n+from plone.app.testing import TEST_USER_PASSWORD\n from plone.rest.testing import PLONE_REST_FUNCTIONAL_TESTING\n \n import json\n@@ -13,7 +12,6 @@\n \n \n class TestErrorHandling(unittest.TestCase):\n-\n layer = PLONE_REST_FUNCTIONAL_TESTING\n \n def setUp(self):\n@@ -83,7 +81,7 @@ def test_500_internal_server_error(self):\n self.assertEqual("HTTPError", response.json()["type"])\n \n self.assertEqual(\n- {u"type": u"HTTPError", u"message": u"HTTP Error 500: InternalServerError"},\n+ {"type": "HTTPError", "message": "HTTP Error 500: InternalServerError"},\n response.json(),\n )\n \n@@ -94,7 +92,7 @@ def test_500_traceback_only_for_manager_users(self):\n headers={"Accept": "application/json"},\n auth=(TEST_USER_ID, TEST_USER_PASSWORD),\n )\n- self.assertNotIn(u"traceback", response.json())\n+ self.assertNotIn("traceback", response.json())\n \n # Manager user\n response = requests.get(\n@@ -102,10 +100,10 @@ def test_500_traceback_only_for_manager_users(self):\n headers={"Accept": "application/json"},\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n- self.assertIn(u"traceback", response.json())\n+ self.assertIn("traceback", response.json())\n \n- traceback = response.json()[u"traceback"]\n+ traceback = response.json()["traceback"]\n self.assertIsInstance(traceback, list)\n- self.assertRegexpMatches(\n+ self.assertRegex(\n traceback[0], r\'^File "[^"]*", line \\d*, in (publish|transaction_pubevents)\'\n )\ndiff --git a/src/plone/rest/tests/test_named_services.py b/src/plone/rest/tests/test_named_services.py\nindex fe1977b..05feb60 100644\n--- a/src/plone/rest/tests/test_named_services.py\n+++ b/src/plone/rest/tests/test_named_services.py\n@@ -1,17 +1,15 @@\n-# -*- coding: utf-8 -*-\n from plone.app.testing import setRoles\n-from plone.app.testing import TEST_USER_ID\n from plone.app.testing import SITE_OWNER_NAME\n from plone.app.testing import SITE_OWNER_PASSWORD\n+from plone.app.testing import TEST_USER_ID\n from plone.rest.testing import PLONE_REST_FUNCTIONAL_TESTING\n \n-import unittest\n import requests\n import transaction\n+import unittest\n \n \n class TestNamedServiceEndpoints(unittest.TestCase):\n-\n layer = PLONE_REST_FUNCTIONAL_TESTING\n \n def setUp(self):\n@@ -31,7 +29,7 @@ def test_dexterity_named_get(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual({u"service": u"named get"}, response.json())\n+ self.assertEqual({"service": "named get"}, response.json())\n \n def test_dexterity_named_post(self):\n response = requests.post(\n@@ -40,7 +38,7 @@ def test_dexterity_named_post(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual({u"service": u"named post"}, response.json())\n+ self.assertEqual({"service": "named post"}, response.json())\n \n def test_dexterity_named_put(self):\n response = requests.put(\n@@ -49,7 +47,7 @@ def test_dexterity_named_put(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual({u"service": u"named put"}, response.json())\n+ self.assertEqual({"service": "named put"}, response.json())\n \n def test_dexterity_named_patch(self):\n response = requests.patch(\n@@ -58,7 +56,7 @@ def test_dexterity_named_patch(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual({u"service": u"named patch"}, response.json())\n+ self.assertEqual({"service": "named patch"}, response.json())\n \n def test_dexterity_named_delete(self):\n response = requests.delete(\n@@ -67,7 +65,7 @@ def test_dexterity_named_delete(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual({u"service": u"named delete"}, response.json())\n+ self.assertEqual({"service": "named delete"}, response.json())\n \n def test_dexterity_named_options(self):\n response = requests.options(\n@@ -76,4 +74,4 @@ def test_dexterity_named_options(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual({u"service": u"named options"}, response.json())\n+ self.assertEqual({"service": "named options"}, response.json())\ndiff --git a/src/plone/rest/tests/test_negotiation.py b/src/plone/rest/tests/test_negotiation.py\nindex 70edd5a..3978b88 100644\n--- a/src/plone/rest/tests/test_negotiation.py\n+++ b/src/plone/rest/tests/test_negotiation.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from plone.rest.negotiation import lookup_service_id\n from plone.rest.negotiation import parse_accept_header\n from plone.rest.negotiation import register_service\n@@ -61,24 +60,24 @@ def test_parse_invalid_accept_header(self):\n class TestServiceRegistry(unittest.TestCase):\n def test_register_media_type(self):\n self.assertEqual(\n- u"GET_application_json_", register_service("GET", ("application", "json"))\n+ "GET_application_json_", register_service("GET", ("application", "json"))\n )\n self.assertEqual(\n- u"GET_application_json_", lookup_service_id("GET", "application/json")\n+ "GET_application_json_", lookup_service_id("GET", "application/json")\n )\n \n def test_register_wildcard_subtype(self):\n- self.assertEqual(u"PATCH_text_*_", register_service("PATCH", ("text", "*")))\n- self.assertEqual(u"PATCH_text_*_", lookup_service_id("PATCH", "text/xml"))\n+ self.assertEqual("PATCH_text_*_", register_service("PATCH", ("text", "*")))\n+ self.assertEqual("PATCH_text_*_", lookup_service_id("PATCH", "text/xml"))\n \n def test_register_wilcard_type(self):\n- self.assertEqual(u"PATCH_*_*_", register_service("PATCH", ("*", "*")))\n- self.assertEqual(u"PATCH_*_*_", lookup_service_id("PATCH", "foo/bar"))\n+ self.assertEqual("PATCH_*_*_", register_service("PATCH", ("*", "*")))\n+ self.assertEqual("PATCH_*_*_", lookup_service_id("PATCH", "foo/bar"))\n \n def test_service_id_for_multiple_media_types_is_none(self):\n register_service("GET", "application/json")\n self.assertEqual(\n- None, lookup_service_id("GET", "application/json,application/javascipt")\n+ None, lookup_service_id("GET", "application/json,application/javascript")\n )\n \n def test_service_id_for_invalid_media_type_is_none(self):\ndiff --git a/src/plone/rest/tests/test_permissions.py b/src/plone/rest/tests/test_permissions.py\nindex 2ab0df6..166b097 100644\n--- a/src/plone/rest/tests/test_permissions.py\n+++ b/src/plone/rest/tests/test_permissions.py\n@@ -1,23 +1,21 @@\n-# -*- coding: utf-8 -*-\n-from Products.CMFCore.utils import getToolByName\n-from ZPublisher.pubevents import PubStart\n from base64 import b64encode\n+from plone.app.testing import login\n+from plone.app.testing import setRoles\n from plone.app.testing import SITE_OWNER_NAME\n from plone.app.testing import TEST_USER_ID\n from plone.app.testing import TEST_USER_NAME\n from plone.app.testing import TEST_USER_PASSWORD\n-from plone.app.testing import login\n-from plone.app.testing import setRoles\n from plone.rest.service import Service\n from plone.rest.testing import PLONE_REST_INTEGRATION_TESTING\n+from Products.CMFCore.utils import getToolByName\n from zExceptions import Unauthorized\n from zope.event import notify\n+from ZPublisher.pubevents import PubStart\n \n import unittest\n \n \n class TestPermissions(unittest.TestCase):\n-\n layer = PLONE_REST_INTEGRATION_TESTING\n \n def setUp(self):\n@@ -36,7 +34,7 @@ def traverse(self, path="/plone", accept="application/json", method="GET"):\n request.environ["PATH_TRANSLATED"] = path\n request.environ["HTTP_ACCEPT"] = accept\n request.environ["REQUEST_METHOD"] = method\n- auth = "%s:%s" % (TEST_USER_NAME, TEST_USER_PASSWORD)\n+ auth = f"{TEST_USER_NAME}:{TEST_USER_PASSWORD}"\n b64auth = b64encode(auth.encode("utf8"))\n request._auth = "Basic %s" % b64auth.decode("utf8")\n notify(PubStart(request))\ndiff --git a/src/plone/rest/tests/test_redirects.py b/src/plone/rest/tests/test_redirects.py\nindex 30122da..a172a4d 100644\n--- a/src/plone/rest/tests/test_redirects.py\n+++ b/src/plone/rest/tests/test_redirects.py\n@@ -1,4 +1,3 @@\n-# -*- coding: utf-8 -*-\n from BTrees.OOBTree import OOSet\n from plone.app.redirector.interfaces import IRedirectionStorage\n from plone.app.testing import setRoles\n@@ -15,7 +14,6 @@\n \n \n class TestRedirects(unittest.TestCase):\n-\n layer = PLONE_REST_FUNCTIONAL_TESTING\n \n def setUp(self):\ndiff --git a/src/plone/rest/tests/test_siteroot.py b/src/plone/rest/tests/test_siteroot.py\nindex 6cf71d5..9e99293 100644\n--- a/src/plone/rest/tests/test_siteroot.py\n+++ b/src/plone/rest/tests/test_siteroot.py\n@@ -1,16 +1,14 @@\n-# -*- coding: utf-8 -*-\n-from plone.rest.testing import PLONE_REST_FUNCTIONAL_TESTING\n from plone.app.testing import setRoles\n-from plone.app.testing import TEST_USER_ID\n from plone.app.testing import SITE_OWNER_NAME\n from plone.app.testing import SITE_OWNER_PASSWORD\n+from plone.app.testing import TEST_USER_ID\n+from plone.rest.testing import PLONE_REST_FUNCTIONAL_TESTING\n \n-import unittest\n import requests\n+import unittest\n \n \n class TestSiteRootServiceEndpoints(unittest.TestCase):\n-\n layer = PLONE_REST_FUNCTIONAL_TESTING\n \n def setUp(self):\n@@ -33,8 +31,8 @@ def test_siteroot_get(self):\n response.status_code\n ),\n )\n- self.assertEqual(u"plone", response.json().get("id"))\n- self.assertEqual(u"GET", response.json().get("method"))\n+ self.assertEqual("plone", response.json().get("id"))\n+ self.assertEqual("GET", response.json().get("method"))\n \n def test_siteroot_post(self):\n response = requests.post(\n@@ -49,8 +47,8 @@ def test_siteroot_post(self):\n response.status_code\n ),\n )\n- self.assertEqual(u"plone", response.json().get("id"))\n- self.assertEqual(u"POST", response.json().get("method"))\n+ self.assertEqual("plone", response.json().get("id"))\n+ self.assertEqual("POST", response.json().get("method"))\n \n def test_siteroot_delete(self):\n response = requests.delete(\n@@ -59,8 +57,8 @@ def test_siteroot_delete(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual(u"plone", response.json().get("id"))\n- self.assertEqual(u"DELETE", response.json().get("method"))\n+ self.assertEqual("plone", response.json().get("id"))\n+ self.assertEqual("DELETE", response.json().get("method"))\n \n def test_siteroot_put(self):\n response = requests.put(\n@@ -70,8 +68,8 @@ def test_siteroot_put(self):\n )\n \n self.assertEqual(response.status_code, 200)\n- self.assertEqual(u"plone", response.json().get("id"))\n- self.assertEqual(u"PUT", response.json().get("method"))\n+ self.assertEqual("plone", response.json().get("id"))\n+ self.assertEqual("PUT", response.json().get("method"))\n \n def test_siteroot_patch(self):\n response = requests.patch(\n@@ -81,8 +79,8 @@ def test_siteroot_patch(self):\n )\n \n self.assertEqual(response.status_code, 200)\n- self.assertEqual(u"plone", response.json().get("id"))\n- self.assertEqual(u"PATCH", response.json().get("method"))\n+ self.assertEqual("plone", response.json().get("id"))\n+ self.assertEqual("PATCH", response.json().get("method"))\n \n def test_siteroot_options(self):\n response = requests.options(\n@@ -91,5 +89,5 @@ def test_siteroot_options(self):\n auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),\n )\n self.assertEqual(response.status_code, 200)\n- self.assertEqual(u"plone", response.json().get("id"))\n- self.assertEqual(u"OPTIONS", response.json().get("method"))\n+ self.assertEqual("plone", response.json().get("id"))\n+ self.assertEqual("OPTIONS", response.json().get("method"))\ndiff --git a/src/plone/rest/tests/test_traversal.py b/src/plone/rest/tests/test_traversal.py\nindex 5d5389d..43ee632 100644\n--- a/src/plone/rest/tests/test_traversal.py\n+++ b/src/plone/rest/tests/test_traversal.py\n@@ -1,7 +1,3 @@\n-# -*- coding: utf-8 -*-\n-from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster\n-from ZPublisher import BeforeTraverse\n-from ZPublisher.pubevents import PubStart\n from base64 import b64encode\n from plone.app.layout.navigation.interfaces import INavigationRoot\n from plone.app.testing import setRoles\n@@ -10,17 +6,19 @@\n from plone.app.testing import TEST_USER_ID\n from plone.rest.service import Service\n from plone.rest.testing import PLONE_REST_INTEGRATION_TESTING\n+from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster\n from zExceptions import NotFound\n from zExceptions import Redirect\n from zope.event import notify\n from zope.interface import alsoProvides\n from zope.publisher.interfaces.browser import IBrowserView\n+from ZPublisher import BeforeTraverse\n+from ZPublisher.pubevents import PubStart\n \n import unittest\n \n \n class TestTraversal(unittest.TestCase):\n-\n layer = PLONE_REST_INTEGRATION_TESTING\n \n def setUp(self):\n@@ -34,7 +32,7 @@ def traverse(self, path="/plone", accept="application/json", method="GET"):\n request.environ["PATH_TRANSLATED"] = path\n request.environ["HTTP_ACCEPT"] = accept\n request.environ["REQUEST_METHOD"] = method\n- auth = "%s:%s" % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)\n+ auth = f"{SITE_OWNER_NAME}:{SITE_OWNER_PASSWORD}"\n b64auth = b64encode(auth.encode("utf8"))\n request._auth = "Basic %s" % b64auth.decode("utf8")\n notify(PubStart(request))\ndiff --git a/src/plone/rest/traverse.py b/src/plone/rest/traverse.py\nindex 0a151c8..bd21031 100644\n--- a/src/plone/rest/traverse.py\n+++ b/src/plone/rest/traverse.py\n@@ -1,24 +1,23 @@\n-# -*- coding: utf-8 -*-\n-from Products.CMFCore.interfaces import ISiteRoot\n-from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster\n-from ZPublisher.BaseRequest import DefaultPublishTraverse\n+from plone.rest.events import mark_as_api_request\n from plone.rest.interfaces import IAPIRequest\n from plone.rest.interfaces import IService\n-from plone.rest.events import mark_as_api_request\n+from Products.CMFCore.interfaces import IContentish\n+from Products.CMFCore.interfaces import ISiteRoot\n+from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster\n from zExceptions import Redirect\n from zope.component import adapter\n from zope.component import queryMultiAdapter\n from zope.interface import implementer\n from zope.publisher.interfaces.browser import IBrowserPublisher\n from zope.traversing.interfaces import ITraversable\n-from Products.CMFCore.interfaces import IContentish\n+from ZPublisher.BaseRequest import DefaultPublishTraverse\n \n \n @adapter(ISiteRoot, IAPIRequest)\n class RESTTraverse(DefaultPublishTraverse):\n def publishTraverse(self, request, name):\n try:\n- obj = super(RESTTraverse, self).publishTraverse(request, name)\n+ obj = super().publishTraverse(request, name)\n if not IContentish.providedBy(obj) and not IService.providedBy(obj):\n if isinstance(obj, VirtualHostMonster):\n return obj\n@@ -49,12 +48,12 @@ def publishTraverse(self, request, name):\n \n def browserDefault(self, request):\n # Called when we have reached the end of the path\n- # In our case this means an unamed service\n+ # In our case this means an unnamed service\n return self.context, (request._rest_service_id,)\n \n \n @implementer(ITraversable)\n-class MarkAsRESTTraverser(object):\n+class MarkAsRESTTraverser:\n """\n Traversal adapter for the ``++api++`` namespace.\n It marks the request as API request.\n@@ -82,7 +81,7 @@ def traverse(self, name_ignored, subpath_ignored):\n \n \n @implementer(IBrowserPublisher)\n-class RESTWrapper(object):\n+class RESTWrapper:\n """A wrapper for objects traversed during a REST request."""\n \n def __init__(self, context, request):\n@@ -99,7 +98,7 @@ def __getitem__(self, name):\n # Delegate key access to the wrapped object\n return self.context[name]\n \n- # MultiHook requries this to be a class attribute\n+ # MultiHook requires this to be a class attribute\n def __before_publishing_traverse__(self, arg1, arg2=None):\n bpth = getattr(self.context, "__before_publishing_traverse__", False)\n if bpth:\n@@ -135,5 +134,5 @@ def publishTraverse(self, request, name):\n \n def browserDefault(self, request):\n # Called when we have reached the end of the path\n- # In our case this means an unamed service\n+ # In our case this means an unnamed service\n return self.context, (request._rest_service_id,)\ndiff --git a/src/plone/rest/zcml.py b/src/plone/rest/zcml.py\nindex 3e33602..67c108f 100644\n--- a/src/plone/rest/zcml.py\n+++ b/src/plone/rest/zcml.py\n@@ -1,13 +1,12 @@\n-# -*- coding: utf-8 -*-\n from AccessControl.class_init import InitializeClass\n from AccessControl.security import getSecurityInfo\n from AccessControl.security import protectClass\n-from Products.Five.browser import BrowserView\n from plone.rest.cors import CORSPolicy\n from plone.rest.cors import register_method_for_preflight\n from plone.rest.interfaces import ICORSPolicy\n from plone.rest.negotiation import parse_accept_header\n from plone.rest.negotiation import register_service\n+from Products.Five.browser import BrowserView\n from zope.browserpage.metaconfigure import _handle_for\n from zope.component.zcml import handler\n from zope.configuration.fields import GlobalInterface\n@@ -23,48 +22,48 @@ class IService(Interface):\n """ """\n \n method = TextLine(\n- title=u"The name of the view that should be the default. "\n- + u"[get|post|put|delete]",\n- description=u"""\n+ title="The name of the view that should be the default. "\n+ + "[get|post|put|delete]",\n+ description="""\n This name refers to view that should be the view used by\n default (if no view name is supplied explicitly).""",\n )\n \n accept = TextLine(\n- title=u"Acceptable media types",\n- description=u"""Specifies the media type used for content negotiation.\n+ title="Acceptable media types",\n+ description="""Specifies the media type used for content negotiation.\n The service is limited to the given media type and only called if the\n request contains an "Accept" header with the given media type. Multiple\n media types can be given by separating them with a comma.""",\n- default=u"application/json",\n+ default="application/json",\n )\n \n for_ = GlobalObject(\n- title=u"The interface this view is the default for.",\n- description=u"""Specifies the interface for which the view is\n+ title="The interface this view is the default for.",\n+ description="""Specifies the interface for which the view is\n registered. All objects implementing this interface can make use of\n this view. If this attribute is not specified, the view is available\n for all objects.""",\n )\n \n factory = GlobalObject(\n- title=u"The factory for this service",\n- description=u"The factory is usually subclass of the Service class.",\n+ title="The factory for this service",\n+ description="The factory is usually subclass of the Service class.",\n )\n \n name = TextLine(\n- title=u"The name of the service.",\n- description=u"""When no name is defined, the service is available at\n+ title="The name of the service.",\n+ description="""When no name is defined, the service is available at\n the object\'s absolute URL. When defining a name, the service is\n available at the object\'s absolute URL appended with a slash and the\n service name.""",\n required=False,\n- default=u"",\n+ default="",\n )\n \n layer = GlobalInterface(\n- title=u"The browser layer for which this service is registered.",\n- description=u"""Useful for overriding existing services or for making\n+ title="The browser layer for which this service is registered.",\n+ description="""Useful for overriding existing services or for making\n services available only if a specific add-on has been\n installed.""",\n required=False,\n@@ -72,8 +71,8 @@ class IService(Interface):\n )\n \n permission = Permission(\n- title=u"Permission",\n- description=u"The permission needed to access the service.",\n+ title="Permission",\n+ description="The permission needed to access the service.",\n required=True,\n )\n \n@@ -86,9 +85,8 @@ def serviceDirective(\n for_,\n permission,\n layer=IDefaultBrowserLayer,\n- name=u"",\n+ name="",\n ):\n-\n _handle_for(_context, for_)\n \n media_types = parse_accept_header(accept)\n@@ -137,16 +135,16 @@ class ICORSPolicyDirective(Interface):\n """Directive for defining CORS policies"""\n \n for_ = GlobalObject(\n- title=u"The interface this CORS policy is for.",\n- description=u"""Specifies the interface for which the CORS policy is\n+ title="The interface this CORS policy is for.",\n+ description="""Specifies the interface for which the CORS policy is\n registered. If this attribute is not specified, the CORS policy applies\n to all objects.""",\n required=False,\n )\n \n layer = GlobalInterface(\n- title=u"The browser layer for which this CORS policy is registered.",\n- description=u"""Useful for overriding existing policies or for making\n+ title="The browser layer for which this CORS policy is registered.",\n+ description="""Useful for overriding existing policies or for making\n them available only if a specific add-on has been\n installed.""",\n required=False,\n@@ -154,45 +152,45 @@ class ICORSPolicyDirective(Interface):\n )\n \n allow_origin = TextLine(\n- title=u"Origins",\n- description=u"""Origins that are allowed access to the resource. Either\n+ title="Origins",\n+ description="""Origins that are allowed access to the resource. Either\n a comma separated list of origins, e.g. "http://example.net,\n http://mydomain.com" or "*".""",\n )\n \n allow_methods = TextLine(\n- title=u"Methods",\n- description=u"""A comma separated list of HTTP method names that are\n+ title="Methods",\n+ description="""A comma separated list of HTTP method names that are\n allowed by this CORS policy, e.g. "DELETE,GET,OPTIONS,PATCH,POST,PUT".\n """,\n required=False,\n )\n \n allow_headers = TextLine(\n- title=u"Headers",\n- description=u"""A comma separated list of request headers allowed to be\n+ title="Headers",\n+ description="""A comma separated list of request headers allowed to be\n sent by the client, e.g. "X-My-Header".""",\n required=False,\n )\n \n expose_headers = TextLine(\n- title=u"Exposed Headers",\n- description=u"""A comma separated list of response headers clients can\n+ title="Exposed Headers",\n+ description="""A comma separated list of response headers clients can\n access, e.g. "Content-Length,X-My-Header".""",\n required=False,\n )\n \n allow_credentials = Bool(\n- title=u"Support Credentials",\n- description=u"""Indicates whether the resource supports user\n+ title="Support Credentials",\n+ description="""Indicates whether the resource supports user\n credentials in the request.""",\n required=True,\n default=False,\n )\n \n max_age = TextLine(\n- title=u"Max Age",\n- description=u"""Indicates how long the results of a preflight request\n+ title="Max Age",\n+ description="""Indicates how long the results of a preflight request\n can be cached.""",\n required=False,\n )\n@@ -209,7 +207,6 @@ def cors_policy_directive(\n for_=Interface,\n layer=IDefaultBrowserLayer,\n ):\n-\n _handle_for(_context, for_)\n \n # Create a new policy class and store the CORS policy configuration in\n@@ -240,7 +237,7 @@ def cors_policy_directive(\n new_class,\n (for_, layer),\n ICORSPolicy,\n- u"",\n+ "",\n _context.info,\n ),\n )\ndiff --git a/versions.cfg b/versions.cfg\nindex 226cfde..ed31954 100644\n--- a/versions.cfg\n+++ b/versions.cfg\n@@ -14,7 +14,7 @@ Pygments = 2.5.1\n plone.recipe.varnish = 1.3\n \n # Code-analysis\n-black = 21.12b0\n+black = 23.3.0\n plone.recipe.codeanalysis = 3.0.1\n coverage = 3.7.1\n pep8 = 1.7.1\n' +b'diff --git a/docs/source/endpoints/types.md b/docs/source/endpoints/types.md\nindex d2ea10ead..e12f66b75 100644\n--- a/docs/source/endpoints/types.md\n+++ b/docs/source/endpoints/types.md\n@@ -107,7 +107,7 @@ To get the schema of a content type, access the `/@types` endpoint with the name\n :language: http\n ```\n \n-The content type schema uses the [JSON Schema](http://json-schema.org/) format.\n+The content type schema uses the [JSON Schema](https://json-schema.org/) format.\n The tagged values for the widgets are also exposed in the `properties` attribute of the schema.\n \n For `Choice` fields, their vocabulary or source will be linked to in a `vocabulary` or `querysource` property (one or the other, never both):\ndiff --git a/news/1718.documentation b/news/1718.documentation\nnew file mode 100644\nindex 000000000..acca3051e\n--- /dev/null\n+++ b/news/1718.documentation\n@@ -0,0 +1 @@\n+Fix redirect for https://json-schema.org/. @stevepiercy\n'