Skip to content

Commit 094c1e0

Browse files
committed
Add filter endpoint tests, switch to unittest provided asserts, make pylint happier
1 parent b050663 commit 094c1e0

20 files changed

+1177
-860
lines changed

.pylintrc

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
extension-pkg-whitelist=
77

88
# Specify a score threshold to be exceeded before program exits with error.
9-
fail-under=10.0
9+
fail-under=10
1010

1111
# Add files or directories to the blacklist. They should be base names, not
1212
# paths.
@@ -589,6 +589,3 @@ valid-metaclass-classmethod-first-arg=cls
589589
# "BaseException, Exception".
590590
overgeneral-exceptions=BaseException,
591591
Exception
592-
593-
[MESSAGES CONTROL]
594-
disable = C0330, C0326

gramps_webapi/api/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ def register_endpt(resource: Type[Resource], url: str, name: str):
8080
register_endpt(BookmarkResource, "/bookmarks/<string:namespace>", "bookmark")
8181
register_endpt(BookmarksResource, "/bookmarks/", "bookmarks")
8282
# Filter
83-
register_endpt(FilterResource, "/filters/<string:namespace>", "filter")
83+
register_endpt(FilterResource, "/filters/<string:namespace>/<string:name>", "filter")
84+
register_endpt(FilterResource, "/filters/<string:namespace>", "filters")
8485
# Translate
8586
register_endpt(TranslationResource, "/translations/<string:isocode>", "translation")
8687
register_endpt(TranslationsResource, "/translations/", "translations")

gramps_webapi/api/resources/filters.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from flask import Response, abort
88
from gramps.gen.db.base import DbReadBase
99
from gramps.gen.filters import GenericFilter
10-
from marshmallow import Schema, ValidationError, fields
11-
from webargs import validate
10+
from marshmallow import Schema
11+
from webargs import ValidationError, fields, validate
1212
from webargs.flaskparser import use_args
1313

1414
from ...const import GRAMPS_NAMESPACES
@@ -34,8 +34,9 @@ def get_filter_rules(args: Dict[str, str], namespace: str) -> List[Dict]:
3434
rule_list = []
3535
for rule_class in _RULES_LOOKUP[namespace]:
3636
add_rule = True
37-
if args.get("rule") and args["rule"] != rule_class.__name__:
38-
add_rule = False
37+
if "rules" in args and args["rules"]:
38+
if rule_class.__name__ not in args["rules"]:
39+
add_rule = False
3940
if add_rule:
4041
rule_list.append(
4142
{
@@ -46,6 +47,8 @@ def get_filter_rules(args: Dict[str, str], namespace: str) -> List[Dict]:
4647
"rule": rule_class.__name__,
4748
}
4849
)
50+
if "rules" in args and len(args["rules"]) != len(rule_list):
51+
abort(404)
4952
return rule_list
5053

5154

@@ -55,8 +58,9 @@ def get_custom_filters(args: Dict[str, str], namespace: str) -> List[Dict]:
5558
filters.reload_custom_filters()
5659
for filter_class in filters.CustomFilters.get_filters(namespace):
5760
add_filter = True
58-
if args.get("filter") and args["filter"] != filter_class.get_name():
59-
add_filter = False
61+
if "filters" in args and args["filters"]:
62+
if filter_class.get_name() not in args["filters"]:
63+
add_filter = False
6064
if add_filter:
6165
filter_list.append(
6266
{
@@ -74,6 +78,8 @@ def get_custom_filters(args: Dict[str, str], namespace: str) -> List[Dict]:
7478
],
7579
}
7680
)
81+
if "filters" in args and len(args["filters"]) != len(filter_list):
82+
abort(404)
7783
return filter_list
7884

7985

@@ -113,7 +119,7 @@ def apply_filter(db_handle: DbReadBase, args: Dict, namespace: str) -> List[Hand
113119
for filter_class in filters.CustomFilters.get_filters(namespace):
114120
if args["filter"] == filter_class.get_name():
115121
return filter_class.apply(db_handle)
116-
abort(400)
122+
abort(404)
117123

118124
try:
119125
filter_parms = FilterSchema().load(json.loads(args["rules"]))
@@ -143,21 +149,31 @@ class FilterSchema(Schema):
143149
validate=validate.OneOf(["and", "or", "xor", "one"]),
144150
)
145151
invert = fields.Boolean(required=False, missing=False)
146-
rules = fields.List(fields.Nested(RuleSchema))
152+
rules = fields.List(
153+
fields.Nested(RuleSchema), required=True, validate=validate.Length(min=1)
154+
)
147155

148156

149157
class CustomFilterSchema(FilterSchema):
150158
"""Structure for a custom filter."""
151159

152160
name = fields.Str(required=True, validate=validate.Length(min=1))
153161
comment = fields.Str(required=False)
162+
rules = fields.List(
163+
fields.Nested(RuleSchema), required=True, validate=validate.Length(min=1)
164+
)
154165

155166

156167
class FilterResource(ProtectedResource, GrampsJSONEncoder):
157168
"""Filter resource."""
158169

159170
@use_args(
160-
{"filter": fields.Str(), "rule": fields.Str()},
171+
{
172+
"filters": fields.DelimitedList(
173+
fields.Str(validate=validate.Length(min=1))
174+
),
175+
"rules": fields.DelimitedList(fields.Str(validate=validate.Length(min=1))),
176+
},
161177
location="query",
162178
)
163179
def get(self, args: Dict[str, str], namespace: str) -> Response:
@@ -168,10 +184,10 @@ def get(self, args: Dict[str, str], namespace: str) -> Response:
168184
abort(404)
169185

170186
rule_list = get_filter_rules(args, namespace)
171-
if args.get("rule") and not args.get("filter"):
187+
if "rules" in args and "filters" not in args:
172188
return self.response(200, {"rules": rule_list})
173189
filter_list = get_custom_filters(args, namespace)
174-
if args.get("filter") and not args.get("rule"):
190+
if "filters" in args and "rules" not in args:
175191
return self.response(200, {"filters": filter_list})
176192
return self.response(200, {"filters": filter_list, "rules": rule_list})
177193

@@ -214,12 +230,11 @@ def put(self, args: Dict, namespace: str) -> Response:
214230

215231
@use_args(
216232
{
217-
"name": fields.Str(required=True),
218-
"force": fields.Boolean(required=False, missing=False),
233+
"force": fields.Str(validate=validate.Length(equal=0)),
219234
},
220-
location="json",
235+
location="query",
221236
)
222-
def delete(self, args: Dict, namespace: str) -> Response:
237+
def delete(self, args: Dict, namespace: str, name: str) -> Response:
223238
"""Delete a custom filter."""
224239
try:
225240
namespace = GRAMPS_NAMESPACES[namespace]
@@ -229,17 +244,15 @@ def delete(self, args: Dict, namespace: str) -> Response:
229244
filters.reload_custom_filters()
230245
custom_filters = filters.CustomFilters.get_filters(namespace)
231246
for custom_filter in custom_filters:
232-
if args["name"] == custom_filter.get_name():
247+
if name == custom_filter.get_name():
233248
filter_set = set()
234249
self._find_dependent_filters(namespace, custom_filter, filter_set)
235250
if len(filter_set) > 1:
236251
if "force" not in args or not args["force"]:
237252
abort(405)
238253
list(map(custom_filters.remove, filter_set))
239254
filters.CustomFilters.save()
240-
return self.response(
241-
200, {"message": "Deleted filter: " + args["name"]}
242-
)
255+
return self.response(200, {"message": "Deleted filter: " + name})
243256
return abort(404)
244257

245258
def _find_dependent_filters(

gramps_webapi/data/apispec.yaml

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1825,16 +1825,16 @@ paths:
18251825
required: true
18261826
type: string
18271827
description: "The namespace or category for the filters."
1828-
- name: rule
1828+
- name: rules
18291829
in: query
18301830
required: false
18311831
type: string
1832-
description: "The name of a specific filter rule to be returned."
1833-
- name: filter
1832+
description: "A comma delimited list of specific filter rules to be returned."
1833+
- name: filters
18341834
in: query
18351835
required: false
18361836
type: string
1837-
description: "The name of a specific custom filter to be returned."
1837+
description: "A comma delimited list of specific custom filters to be returned."
18381838
responses:
18391839
200:
18401840
description: "OK: Successful operation."
@@ -1908,6 +1908,7 @@ paths:
19081908
403:
19091909
description: "Forbidden: Bad credentials, authentication failed."
19101910

1911+
/filters/{namespace}/{name}:
19111912
delete:
19121913
tags:
19131914
- filters
@@ -1921,12 +1922,16 @@ paths:
19211922
required: true
19221923
type: string
19231924
description: "The namespace or category for the custom filter."
1924-
- name: filter
1925-
in: body
1925+
- name: name
1926+
in: path
19261927
required: true
1927-
description: "The custom filter to delete."
1928-
schema:
1929-
$ref: "#/definitions/CustomFilterObject"
1928+
type: string
1929+
description: "The name of the custom filter."
1930+
- name: force
1931+
in: query
1932+
required: false
1933+
type: string
1934+
description: "Force delete custom filter and all filters that depend upon it."
19301935
responses:
19311936
200:
19321937
description: "OK: Successful operation."
@@ -4045,24 +4050,6 @@ definitions:
40454050
items:
40464051
$ref: "#/definitions/FilterRule"
40474052

4048-
##############################################################################
4049-
# Model - CustomFilterObject
4050-
##############################################################################
4051-
4052-
CustomFilterObject:
4053-
type: object
4054-
required:
4055-
- name
4056-
properties:
4057-
name:
4058-
type: string
4059-
description: "Name of the custom rule to delete."
4060-
example: "MyTestRule"
4061-
force:
4062-
type: boolean
4063-
description: "Indicator to approve deletion of all dependent filters if any exist."
4064-
example: true
4065-
40664053
##############################################################################
40674054
# Model - Translations
40684055
##############################################################################

tests/test_endpoints/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
import gramps_webapi.app
1414
from gramps_webapi.app import create_app
1515
from gramps_webapi.const import ENV_CONFIG_FILE, TEST_EXAMPLE_GRAMPS_CONFIG
16-
17-
from .. import TEST_GRAMPSHOME, ExampleDbSQLite
16+
from tests import TEST_GRAMPSHOME, ExampleDbSQLite
1817

1918

2019
def get_object_count(gramps_object):

0 commit comments

Comments
 (0)