Skip to content

Commit 9a51c41

Browse files
authored
Add GQL filter to objects endpoints (#512)
* Add gramps_ql dependence and version * Clean up test files * Add GQL option to objects endpoints * Try manually upgrading pyparsing * Revert change to workflow * catch one more error * Use gql > 0.3
1 parent 3723458 commit 9a51c41

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

gramps_webapi/api/resources/base.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from abc import abstractmethod
2424
from typing import Dict, List
2525

26+
import gramps_ql as gql
2627
from flask import Response, abort, request
2728
from gramps.gen.const import GRAMPS_LOCALE as glocale
2829
from gramps.gen.db import DbTxn
@@ -31,6 +32,7 @@
3132
from gramps.gen.lib.primaryobj import BasicPrimaryObject as GrampsObject
3233
from gramps.gen.lib.serialize import from_json
3334
from gramps.gen.utils.grampslocale import GrampsLocale
35+
from pyparsing.exceptions import ParseBaseException
3436
from webargs import fields, validate
3537

3638
from ...auth.const import PERM_ADD_OBJ, PERM_DEL_OBJ, PERM_EDIT_OBJ
@@ -343,6 +345,7 @@ class GrampsObjectsResource(GrampsObjectResourceHelper, Resource):
343345
fields.Str(validate=validate.Length(min=1))
344346
),
345347
"format_options": fields.Str(validate=validate.Length(min=1)),
348+
"gql": fields.Str(validate=validate.Length(min=1)),
346349
"gramps_id": fields.Str(validate=validate.Length(min=1)),
347350
"keys": fields.DelimitedList(fields.Str(validate=validate.Length(min=1))),
348351
"locale": fields.Str(
@@ -413,6 +416,16 @@ def get(self, args: Dict) -> Response:
413416
)
414417
objects = [obj for obj in objects if obj.handle in set(handles)]
415418

419+
if "gql" in args:
420+
try:
421+
objects = [
422+
obj
423+
for obj in objects
424+
if gql.match(query=args["gql"], obj=obj, db=self.db_handle)
425+
]
426+
except (ParseBaseException, ValueError, TypeError) as e:
427+
abort_with_message(422, str(e))
428+
416429
if self.gramps_class_name == "Media" and args.get("filemissing"):
417430
objects = filter_missing_files(objects)
418431

gramps_webapi/api/resources/metadata.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
"""Metadata API resource."""
2222

23+
import gramps_ql as gql
2324
import pytesseract
2425
from flask import Response, current_app
2526
from gramps.cli.clidbman import CLIDbManager
@@ -96,6 +97,7 @@ def get(self, args) -> Response:
9697
"schema": VERSION,
9798
"version": VERSION,
9899
},
100+
"gramps_ql": {"version": gql.__version__},
99101
"locale": {
100102
"lang": GRAMPS_LOCALE.lang,
101103
"language": GRAMPS_LOCALE.language[0],

gramps_webapi/data/apispec.yaml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,12 @@ paths:
792792
793793
794794
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
795+
- name: gql
796+
in: query
797+
required: false
798+
type: string
799+
description: "A Gramps QL query string that is used to filter the objects."
800+
example: "media_list.length >= 10"
795801
- name: strip
796802
in: query
797803
required: false
@@ -1382,6 +1388,12 @@ paths:
13821388
13831389
13841390
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
1391+
- name: gql
1392+
in: query
1393+
required: false
1394+
type: string
1395+
description: "A Gramps QL query string that is used to filter the objects."
1396+
example: "media_list.length >= 10"
13851397
- name: strip
13861398
in: query
13871399
required: false
@@ -1857,6 +1869,12 @@ paths:
18571869
18581870
18591871
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
1872+
- name: gql
1873+
in: query
1874+
required: false
1875+
type: string
1876+
description: "A Gramps QL query string that is used to filter the objects."
1877+
example: "media_list.length >= 10"
18601878
- name: strip
18611879
in: query
18621880
required: false
@@ -2238,6 +2256,12 @@ paths:
22382256
22392257
22402258
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
2259+
- name: gql
2260+
in: query
2261+
required: false
2262+
type: string
2263+
description: "A Gramps QL query string that is used to filter the objects."
2264+
example: "media_list.length >= 10"
22412265
- name: strip
22422266
in: query
22432267
required: false
@@ -2561,6 +2585,12 @@ paths:
25612585
25622586
25632587
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
2588+
- name: gql
2589+
in: query
2590+
required: false
2591+
type: string
2592+
description: "A Gramps QL query string that is used to filter the objects."
2593+
example: "media_list.length >= 10"
25642594
- name: strip
25652595
in: query
25662596
required: false
@@ -2872,6 +2902,12 @@ paths:
28722902
28732903
28742904
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
2905+
- name: gql
2906+
in: query
2907+
required: false
2908+
type: string
2909+
description: "A Gramps QL query string that is used to filter the objects."
2910+
example: "media_list.length >= 10"
28752911
- name: strip
28762912
in: query
28772913
required: false
@@ -3155,6 +3191,12 @@ paths:
31553191
31563192
31573193
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
3194+
- name: gql
3195+
in: query
3196+
required: false
3197+
type: string
3198+
description: "A Gramps QL query string that is used to filter the objects."
3199+
example: "media_list.length >= 10"
31583200
- name: strip
31593201
in: query
31603202
required: false
@@ -3450,6 +3492,12 @@ paths:
34503492
34513493
34523494
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
3495+
- name: gql
3496+
in: query
3497+
required: false
3498+
type: string
3499+
description: "A Gramps QL query string that is used to filter the objects."
3500+
example: "note_list.length >= 10"
34533501
- name: strip
34543502
in: query
34553503
required: false
@@ -4137,6 +4185,12 @@ paths:
41374185
41384186
41394187
The regex value is a boolean, true or false, that indicates if text values should be treated as regular expressions. If not present the default is false.
4188+
- name: gql
4189+
in: query
4190+
required: false
4191+
type: string
4192+
description: "A Gramps QL query string that is used to filter the objects."
4193+
example: "tag_list.length >= 10"
41404194
- name: strip
41414195
in: query
41424196
required: false
@@ -9841,6 +9895,14 @@ definitions:
98419895
description: "The version of the Gramps Web API code."
98429896
type: string
98439897
example: "0.1-dev"
9898+
gramps_ql:
9899+
description: "Information about the installed Gramps QL library."
9900+
type: object
9901+
properties:
9902+
version:
9903+
description: "The version of the Gramps Gramps QL library."
9904+
type: string
9905+
example: "0.3.0"
98449906
locale:
98459907
description: "The active locale."
98469908
type: object

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"celery[redis]",
5353
"Unidecode",
5454
"pytesseract",
55+
"gramps-ql>=0.3.0",
5556
]
5657

5758
setup(

tests/test_endpoints/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import gramps_webapi.app
3434
from gramps_webapi.api.util import get_search_indexer
3535
from gramps_webapi.app import create_app
36-
from gramps_webapi.auth import user_db, add_user
36+
from gramps_webapi.auth import add_user, user_db
3737
from gramps_webapi.auth.const import (
3838
ROLE_ADMIN,
3939
ROLE_EDITOR,
@@ -121,4 +121,4 @@ def setUpModule():
121121
def tearDownModule():
122122
"""Test module tear down."""
123123
if TEST_GRAMPSHOME and os.path.isdir(TEST_GRAMPSHOME):
124-
pass # shutil.rmtree(TEST_GRAMPSHOME)
124+
shutil.rmtree(TEST_GRAMPSHOME)

tests/test_endpoints/test_people.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"""Tests for the /api/people endpoints using example_gramps."""
2222

2323
import unittest
24+
from urllib.parse import quote
2425

2526
from . import BASE_URL, get_object_count, get_test_client
2627
from .checks import (
@@ -377,6 +378,35 @@ def test_get_people_parameter_rules_expected_response_invert(self):
377378
if item["gender"] == 1:
378379
self.assertLess(len(item["family_list"]), 2)
379380

381+
def test_get_people_parameter_gql_validate_semantics(self):
382+
"""Test invalid rules syntax."""
383+
check_invalid_semantics(self, TEST_URL + "?gql=()")
384+
385+
def test_get_people_parameter_gql_handle(self):
386+
"""Test invalid rules syntax."""
387+
rv = check_success(
388+
self,
389+
TEST_URL + "?gql=" + quote("gramps_id=I0044"),
390+
)
391+
assert len(rv) == 1
392+
assert rv[0]["gramps_id"] == "I0044"
393+
394+
def test_get_people_parameter_gql_like(self):
395+
"""Test invalid rules syntax."""
396+
rv = check_success(
397+
self,
398+
TEST_URL + "?gql=" + quote("gramps_id ~ I004"),
399+
)
400+
assert len(rv) == 10
401+
402+
def test_get_people_parameter_gql_or(self):
403+
"""Test invalid rules syntax."""
404+
rv = check_success(
405+
self,
406+
TEST_URL + "?gql=" + quote("(gramps_id ~ I004 or gramps_id ~ I003)"),
407+
)
408+
assert len(rv) == 20
409+
380410
def test_get_people_parameter_extend_validate_semantics(self):
381411
"""Test invalid extend parameter and values."""
382412
check_invalid_semantics(self, TEST_URL + "?extend", check="list")

0 commit comments

Comments
 (0)