Skip to content

Commit

Permalink
Navigation is now compatible with the ref/start/end/down table list
Browse files Browse the repository at this point in the history
  • Loading branch information
PonteIneptique committed Aug 26, 2024
1 parent 09f6ab5 commit 3d23820
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 36 deletions.
68 changes: 49 additions & 19 deletions dapitains/app/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import Dict, Any, Optional

from dapitains.tei.document import Document

try:
import uritemplate
from flask import Flask, request, Response
Expand All @@ -13,8 +11,10 @@

import json
import lxml.etree as ET
from dapitains.tei.document import Document
from dapitains.errors import InvalidRangeOrder
from dapitains.app.database import db, Collection, Navigation
from dapitains.app.navigation import get_nav
from dapitains.app.navigation import get_nav, get_member_by_path


def msg_4xx(string, code=404) -> Response:
Expand Down Expand Up @@ -68,7 +68,7 @@ def collection_view(
**(
{
"navigation": templates["navigation"].partial({"resource": related.identifier}).uri,
} if coll.citeStructure else {}
} if related.citeStructure else {}
)
) if related.resource else related.json({
"collection": templates["collection"].partial({"id": related.identifier}).uri
Expand Down Expand Up @@ -124,7 +124,7 @@ def document_view(resource, ref, start, end, tree) -> Response:
)


def navigation_view(resource, ref, start, end, tree, down, templates: Dict[str, str]) -> Response:
def navigation_view(resource, ref, start, end, tree, down, templates: Dict[str, uritemplate.URITemplate]) -> Response:
if not resource:
return msg_4xx("Resource parameter was not provided")

Expand All @@ -146,21 +146,51 @@ def navigation_view(resource, ref, start, end, tree, down, templates: Dict[str,
return msg_4xx(f"You cannot provide a ref parameter as well as start or end", code=400)
elif not ref and ((start and not end) or (end and not start)):
return msg_4xx(f"Range is missing one of its parameters (start or end)", code=400)
else:
if down is None:
return msg_4xx(f"The down query parameter is required when requesting without ref or start/end", code=400)

refs = nav.references[tree]
paths = nav.paths[tree]
members, start, end = get_nav(refs=refs, paths=paths, start_or_ref=start or ref, end=end, down=down)
return Response(json.dumps({
# Start the response
out = {
"@context": "https://distributed-text-services.github.io/specifications/context/1-alpha1.json",
"dtsVersion": "1-alpha",
"@type": "Navigation",
"@id": "https://example.org/api/dts/navigation/?resource=https://en.wikisource.org/wiki/Dracula&down=1",
"resource": collection.json(inject=templates), # To Do: implement and inject URI templates
"member": members
}), mimetype="application/json", status=200)
"@id": templates["navigation"].expand({
"ref": ref, "down": down, "start": start, "end": end, "tree": tree
}),
"resource": collection.json(inject={k:v.uri for k,v in templates.items()}),
}

refs = nav.references[tree]
paths = nav.paths[tree]

# Three first rows of the specs folr combination of down/ref/start/end
if down is None:
if ref:
out["ref"] = get_member_by_path(refs, paths[ref])
elif start and end:
out["start"] = get_member_by_path(refs, paths[start])
out["end"] = get_member_by_path(refs, paths[end])
else:
return msg_4xx(f"The down query parameter is required when requesting without ref or start/end", code=400)
return Response(json.dumps(out), mimetype="application/json", status=200)
elif down == 0 and start and end:
return msg_4xx(f"The down query parameter cannot be `0` while using start/end", code=400)
elif down == 0 and not ref:
return msg_4xx(f"The down query parameter cannot be `0` without using the `ref` parameter", code=400)

try:
members, start, end = get_nav(refs=refs, paths=paths, start_or_ref=start or ref, end=end, down=down)
except InvalidRangeOrder:
return msg_4xx("End reference comes before start in the document order. Interchange start and end.", code=400)
except Exception:
raise

out["member"] = members
if end:
out["start"] = start
out["end"] = end
else:
out["ref"] = start

return Response(json.dumps(out), mimetype="application/json", status=200)


def create_app(
Expand Down Expand Up @@ -212,9 +242,9 @@ def navigation_route():
down = request.args.get("down", type=int, default=None)

return navigation_view(resource, ref, start, end, tree, down, templates={
"navigation": navigation_template.partial({"resource": resource}).uri,
"collection": collection_template.partial({"id": resource}).uri,
"document": document_template.partial({"resource": resource}).uri,
"navigation": navigation_template.partial({"resource": resource}),
"collection": collection_template.partial({"id": resource}),
"document": document_template.partial({"resource": resource}),
})

@app.route("/document/")
Expand Down
47 changes: 31 additions & 16 deletions dapitains/app/navigation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List, Dict, Any, Optional, Tuple
from dapitains.errors import InvalidRangeOrder


def get_member_by_path(data: List[Dict[str, Any]], path: List[int]) -> Optional[Dict[str, Any]]:
Expand Down Expand Up @@ -76,38 +77,52 @@ def get_nav(
"""

paths_index = list(paths.keys())
start_index, end_index = None, None
start_index, end_index = None, len(paths_index)

if end:
end_index = paths_index.index(end) + 1
# For end, as end is inclusive, we check for the last partial match
# (ie, if Mark is [1], we want everything starting
# by [1].)
end_index = paths_index.index(end)
len_end = len(paths[end])
for idx, reference in enumerate(paths_index[end_index+1:]):
# print(paths[:len_end], paths[end])
if paths[reference][:len_end] == paths[end]:
end_index = end_index+idx
else:
break

if start_or_ref:
start_index = paths_index.index(start_or_ref)
if not end:
for index, reference in enumerate(paths_index[start_index+1:]):
if len(paths[start_or_ref]) == len(paths[reference]):
end_index = index + start_index + 1

paths = dict(list(paths.items())[start_index:end_index])

current_level = [0]

if down == 0:
end_index = len(paths_index)
else:
for index, reference in enumerate(paths_index[start_index+1:]):
if len(paths[start_or_ref]) == len(paths[reference]):
end_index = index + start_index
if start_index > end_index:
raise InvalidRangeOrder

paths = dict(list(paths.items())[start_index:end_index+1])

current_level = []
start_path, end_path = None, None

if start_or_ref:
start_path = paths[start_or_ref]
current_level.append(len(start_path))
if end:
end_path = paths[end]
current_level.append(len(end_path))

current_level = max(current_level)

if down == -1:
down = max(list(map(len, paths.values())))
current_level = max(current_level) if current_level else 0

if down == 0:
paths = {key: value for key, value in paths.items() if len(value) == current_level}
elif down == -1:
paths = {key: value for key, value in paths.items() if current_level <= len(value)}
else:
paths = {key: value for key, value in paths.items() if current_level < len(value) <= down + current_level}
paths = {key: value for key, value in paths.items() if current_level <= len(value) <= down + current_level}

return (
[
Expand Down
3 changes: 3 additions & 0 deletions dapitains/errors.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
class UnknownTreeName(Exception):
"""This exception is raised when a requested tree is unknown """

class InvalidRangeOrder(Exception):
"""Error raised when a range is in the wrong order (start > end) """
10 changes: 9 additions & 1 deletion tests/test_db_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ def test_navigation():
}
paths = {tree: generate_paths(ref) for tree, ref in refs.items()}

assert get_nav(refs[doc.default_tree], paths[doc.default_tree], start_or_ref=None, end=None, down=1) == ([
assert get_nav(
refs[doc.default_tree],
paths[doc.default_tree],
start_or_ref=None,
end=None,
down=1
) == ([
{'citeType': 'book', 'ref': 'Luke', "level": 1, "parent": None},
{'citeType': 'book', 'ref': 'Mark', "level": 1, "parent": None}
], None, None), "Check that base function works"
Expand Down Expand Up @@ -90,6 +96,7 @@ def test_navigation():

assert get_nav(refs[doc.default_tree], paths[doc.default_tree], start_or_ref="Luke 1", down=1) == (
[
{'citeType': 'chapter', 'ref': 'Luke 1', "level": 2, "parent": "Luke"},
{'citeType': 'verse', 'ref': 'Luke 1:1', "level": 3, "parent": "Luke 1"},
{'citeType': 'verse', 'ref': 'Luke 1:2', "level": 3, "parent": "Luke 1"},
{'citeType': 'bloup', 'ref': 'Luke 1#1', "level": 3, "parent": "Luke 1"}
Expand All @@ -100,6 +107,7 @@ def test_navigation():

assert get_nav(refs[doc.default_tree], paths[doc.default_tree], start_or_ref="Luke", down=1) == (
[
{'citeType': 'book', 'ref': 'Luke', "level": 1, "parent": None},
{'citeType': 'chapter', 'ref': 'Luke 1', "level": 2, "parent": "Luke"},
],
{'citeType': 'book', 'ref': 'Luke', "level": 1, "parent": None},
Expand Down

0 comments on commit 3d23820

Please sign in to comment.