Skip to content

Commit

Permalink
Merge branch 'main' into maurits-warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
mauritsvanrees authored Jan 22, 2025
2 parents a8727e0 + 5a120c4 commit 374129c
Show file tree
Hide file tree
Showing 27 changed files with 192 additions and 66 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ jobs:
include:
- python-version: "3.8"
plone-version: "5.2"
- python-version: "3.8"
plone-version: "6.0"
- python-version: "3.9"
plone-version: "6.0"
- python-version: "3.10"
Expand Down
19 changes: 19 additions & 0 deletions docs/source/endpoints/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ Example response:
:language: http
```

## Filter list of registry records

```{versionadded} plone.restapi 9.10.0
```

You can filter a list of registry records and batch the results.
To do so, append a query string to the listing endpoint with a `q` parameter and its value set to the prefix of the desired record name.
See {doc}`../usage/batching` for details of how to work with batched results.

```{eval-rst}
.. http:example:: curl httpie python-requests
:request: ../../../src/plone/restapi/tests/http-examples/registry_get_list_filtered.req
```

Example response:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/registry_get_list_filtered.resp
:language: http
```

## Updating registry records

Expand Down
1 change: 1 addition & 0 deletions news/1685.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update CI. @davisagli
1 change: 1 addition & 0 deletions news/1858.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add parse_int to handle all cases of BadRequests from ints passed in. @djay
1 change: 1 addition & 0 deletions news/1861.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In the `@registry` endpoint, added support for filtering the list of registry records. @Faakhir30
1 change: 1 addition & 0 deletions news/1864.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In the `@contextnavigation` endpoint, return `"icon": null` for Files with a mimetype not found in the `content_type_registry`, instead of raising `TypeError`. @mamico
3 changes: 2 additions & 1 deletion plone-6.0.x-python3.8.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[buildout]
extends =
https://dist.plone.org/release/6.0.12/versions.cfg
https://dist.plone.org/release/6.0.14/versions.cfg
base.cfg

[instance]
Expand All @@ -15,3 +15,4 @@ robotframework-assertion-engine = 2.0.0
robotframework-debuglibrary = 2.3.0
robotframework-pythonlibcore = 4.2.0
grpcio-tools = 1.59.0
twine = 5.1.1
17 changes: 2 additions & 15 deletions plone-6.0.x.cfg
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
[buildout]
extends =
https://dist.plone.org/release/6.0.12/versions.cfg
https://dist.plone.org/release/6.0.14/versions.cfg
base.cfg

[buildout:python37]
parts =
test
code-analysis

[instance]
recipe = plone.recipe.zope2instance
zodb-temporary-storage = off

[versions]
# Override pin from Zope. https://github.com/zopefoundation/Zope/issues/1220
docutils = 0.21.2
pygments = 2.14.0
plone.app.linkintegrity = 4.0.3
robotframework-browser = 17.5.2
robotframework-assertion-engine = 2.0.0
robotframework-debuglibrary = 2.3.0
robotframework-pythonlibcore = 4.2.0
grpcio-tools = 1.59.0
twine = 5.1.1
9 changes: 1 addition & 8 deletions plone-6.1.x.cfg
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
[buildout]
extends =
https://dist.plone.org/release/6.1.0a3/versions.cfg
https://dist.plone.org/release/6.1.0b2/versions.cfg
base.cfg

[buildout:python37]
parts =
test
code-analysis

[instance]
recipe = plone.recipe.zope2instance
zodb-temporary-storage = off

[versions]
# Override pin from Zope. https://github.com/zopefoundation/Zope/issues/1220
docutils = 0.21.2
2 changes: 1 addition & 1 deletion requirements-6.0.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-r https://dist.plone.org/release/6.0.12/requirements.txt
-r https://dist.plone.org/release/6.0.14/requirements.txt
2 changes: 1 addition & 1 deletion requirements-6.1.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-r https://dist.plone.org/release/6.1.0a3/requirements.txt
-r https://dist.plone.org/release/6.1.0b2/requirements.txt
16 changes: 9 additions & 7 deletions src/plone/restapi/batching.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from plone.batching.batch import Batch
from plone.restapi.deserializer import json_body
from plone.restapi.deserializer import parse_int
from plone.restapi.exceptions import DeserializationError
from urllib.parse import parse_qsl
from urllib.parse import urlencode
Expand All @@ -14,14 +15,15 @@ def __init__(self, request, results):
self.request = request

try:
self.b_start = int(json_body(self.request).get("b_start", False)) or int(
self.request.form.get("b_start", 0)
)
self.b_size = int(json_body(self.request).get("b_size", False)) or int(
self.request.form.get("b_size", DEFAULT_BATCH_SIZE)
)
except (ValueError, DeserializationError) as e:
data = json_body(request)
except DeserializationError as e:
raise BadRequest(e)
self.b_start = parse_int(data, "b_start", False) or parse_int(
self.request.form, "b_start", 0
)
self.b_size = parse_int(data, "b_size", False) or parse_int(
self.request.form, "b_size", DEFAULT_BATCH_SIZE
)
self.batch = Batch(results, self.b_size, self.b_start)

def __iter__(self):
Expand Down
17 changes: 17 additions & 0 deletions src/plone/restapi/deserializer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from plone.restapi.exceptions import DeserializationError
from zExceptions import BadRequest

import json

Expand Down Expand Up @@ -28,3 +29,19 @@ def boolean_value(value):
"""
return value not in {False, "false", "False", "0", 0}


def parse_int(data, prop, default):
"""
Args:
data: dict from a request
prop: name of a integer paramater in the dict
default: default if not found
Returns: an integer
Raises: BadRequest if not an int
"""
try:
return int(data.get(prop, default))
except (ValueError, TypeError):
raise BadRequest(f"Invalid {prop}: Not an integer")
3 changes: 2 additions & 1 deletion src/plone/restapi/services/contextnavigation/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ def getMimeTypeIcon(self, node):
mtt = getToolByName(self.context, "mimetypes_registry")
if fileo.contentType:
ctype = mtt.lookup(fileo.contentType)
return os.path.join(portal_url, guess_icon_path(ctype[0]))
if ctype:
return os.path.join(portal_url, guess_icon_path(ctype[0]))
except AttributeError:
pass

Expand Down
10 changes: 5 additions & 5 deletions src/plone/restapi/services/discussion/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from plone.app.discussion.browser.comment import EditCommentForm
from plone.app.discussion.browser.comments import CommentForm
from plone.app.discussion.interfaces import IConversation
from plone.restapi.deserializer import json_body
from plone.restapi.deserializer import json_body, parse_int
from plone.restapi.interfaces import ISerializeToJson
from plone.restapi.services import Service
from plone.restapi.services.discussion.utils import can_delete
Expand Down Expand Up @@ -38,7 +38,7 @@ class CommentsGet(Service):

def publishTraverse(self, request, name):
if name:
self.comment_id = int(name)
self.comment_id = parse_int(dict(comment_id=name), "comment_id", None)
return self

def reply(self):
Expand All @@ -57,7 +57,7 @@ class CommentsAdd(Service):

def publishTraverse(self, request, name):
if name:
self.comment_id = int(name)
self.comment_id = parse_int(dict(comment_id=name), "comment_id", None)
request["form.widgets.in_reply_to"] = name
return self

Expand Down Expand Up @@ -96,7 +96,7 @@ class CommentsUpdate(Service):

def publishTraverse(self, request, name):
if name:
self.comment_id = int(name)
self.comment_id = parse_int(dict(comment_id=name), "comment_id", None)
request["form.widgets.comment_id"] = name
return self

Expand Down Expand Up @@ -140,7 +140,7 @@ class CommentsDelete(Service):
comment_id = None

def publishTraverse(self, request, name):
self.comment_id = int(name)
self.comment_id = parse_int(dict(comment_id=name), "comment_id", None)
return self

def reply(self):
Expand Down
10 changes: 2 additions & 8 deletions src/plone/restapi/services/navigation/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from plone.registry.interfaces import IRegistry
from plone.restapi.bbb import INavigationSchema
from plone.restapi.bbb import safe_text
from plone.restapi.deserializer import parse_int
from plone.restapi.interfaces import IExpandableElement
from plone.restapi.serializer.converters import json_compatible
from plone.restapi.services import Service
Expand All @@ -17,7 +18,6 @@
from zope.i18n import translate
from zope.interface import implementer
from zope.interface import Interface
from zExceptions import BadRequest


@implementer(IExpandableElement)
Expand All @@ -29,13 +29,7 @@ def __init__(self, context, request):
self.portal = getSite()

def __call__(self, expand=False):
if self.request.form.get("expand.navigation.depth", False):
try:
self.depth = int(self.request.form["expand.navigation.depth"])
except (ValueError, TypeError) as e:
raise BadRequest(e)
else:
self.depth = 1
self.depth = parse_int(self.request.form, "expand.navigation.depth", 1)

result = {"navigation": {"@id": f"{self.context.absolute_url()}/@navigation"}}
if not expand:
Expand Down
17 changes: 5 additions & 12 deletions src/plone/restapi/services/querystringsearch/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pkg_resources import parse_version
from plone.restapi.bbb import IPloneSiteRoot
from plone.restapi.deserializer import json_body
from plone.restapi.deserializer import parse_int
from plone.restapi.exceptions import DeserializationError
from plone.restapi.interfaces import ISerializeToJson
from plone.restapi.services import Service
Expand Down Expand Up @@ -31,20 +32,12 @@ def __call__(self):
raise BadRequest(str(err))

query = data.get("query", None)
try:
b_start = int(data.get("b_start", 0))
except (ValueError, TypeError):
raise BadRequest("Invalid b_start")
try:
b_size = int(data.get("b_size", 25))
except (ValueError, TypeError):
raise BadRequest("Invalid b_size")

b_start = parse_int(data, "b_start", 0)
b_size = parse_int(data, "b_size", 25)
sort_on = data.get("sort_on", None)
sort_order = data.get("sort_order", None)
try:
limit = int(data.get("limit", 1000))
except (ValueError, TypeError):
raise BadRequest("Invalid limit")
limit = parse_int(data, "limit", 1000)
fullobjects = bool(data.get("fullobjects", False))

if not query:
Expand Down
13 changes: 12 additions & 1 deletion src/plone/restapi/services/registry/get.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from plone.registry import Registry
from plone.registry.interfaces import IRegistry
from plone.restapi.interfaces import ISerializeToJson
from plone.restapi.serializer.converters import json_compatible
Expand Down Expand Up @@ -35,5 +36,15 @@ def reply(self):
value = registry[self._get_record_name]
return json_compatible(value)
else: # batched listing
serializer = getMultiAdapter((registry, self.request), ISerializeToJson)
if q := self.request.form.get("q"):

tmp_registry = Registry()
for key in registry.records.keys():
if key.startswith(q):
tmp_registry.records[key] = registry.records[key]
registry = tmp_registry
serializer = getMultiAdapter(
(registry, self.request),
ISerializeToJson,
)
return serializer()
Original file line number Diff line number Diff line change
Expand Up @@ -423,5 +423,5 @@ Content-Type: application/json
"value": "The person that created an item"
}
],
"items_total": 2973
"items_total": 2974
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GET /plone/@registry?q=Products.CMFPlone HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
HTTP/1.1 200 OK
Content-Type: application/json

{
"@id": "http://localhost:55001/plone/@registry?q=Products.CMFPlone",
"items": [
{
"name": "Products.CMFPlone.i18nl10n.override_dateformat.Enabled",
"schema": {
"properties": {
"description": "Override the translation machinery",
"factory": "Yes/No",
"title": "Enabled",
"type": "boolean"
}
},
"value": false
},
{
"name": "Products.CMFPlone.i18nl10n.override_dateformat.date_format_long",
"schema": {
"properties": {
"description": "Default value: %Y-%m-%d %H:%M (2038-01-19 03:14)",
"factory": "Text line (String)",
"title": "old ZMI property: localLongTimeFormat",
"type": "string"
}
},
"value": "%Y-%m-%d %H:%M"
},
{
"name": "Products.CMFPlone.i18nl10n.override_dateformat.date_format_short",
"schema": {
"properties": {
"description": "Default value: %Y-%m-%d (2038-01-19)",
"factory": "Text line (String)",
"title": "old ZMI property: localTimeFormat",
"type": "string"
}
},
"value": "%Y-%m-%d"
},
{
"name": "Products.CMFPlone.i18nl10n.override_dateformat.time_format",
"schema": {
"properties": {
"description": "Default value: %H:%M (03:14)",
"factory": "Text line (String)",
"title": "old ZMI property: localTimeOnlyFormat",
"type": "string"
}
},
"value": "%H:%M"
}
],
"items_total": 4
}
2 changes: 1 addition & 1 deletion src/plone/restapi/tests/http-examples/site_get.resp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Content-Type: application/json
{
"@id": "http://localhost:55001/plone/@site",
"features": {
"filter_aliases_by_date": false
"filter_aliases_by_date": true
},
"plone.allowed_sizes": [
"huge 1600:65536",
Expand Down
6 changes: 5 additions & 1 deletion src/plone/restapi/tests/test_batching.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ def test_batching_links_omitted_if_resulset_fits_in_single_batch(self):
def test_batching_badrequests(self):
response = self.api_session.get("/collection?b_size=php")
self.assertEqual(response.status_code, 400)
self.assertIn("invalid literal for int()", response.json()["message"])
self.assertIn("Invalid b_size", response.json()["message"])

response = self.api_session.get("/collection?b_size:list=1")
self.assertEqual(response.status_code, 400)
self.assertIn("Invalid b_size", response.json()["message"])


class TestBatchingDXFolders(TestBatchingDXBase):
Expand Down
Loading

0 comments on commit 374129c

Please sign in to comment.