33from __future__ import annotations
44
55import json
6- from collections .abc import Callable , Mapping , Sequence
6+ from collections import defaultdict
7+ from collections .abc import Callable , Sequence
78from functools import partial
8- from typing import Any , cast
9+ from typing import cast
910
11+ import curies
1012import werkzeug
1113import yaml
1214from flask import (
@@ -71,17 +73,48 @@ def _normalize_prefix_or_404(
7173 return norm_prefix
7274
7375
74- def _search (manager_ : Manager , q : str ) -> list [tuple [str , str ]]:
75- q_norm = _norm (q )
76- results = [
77- (prefix , lookup if _norm (prefix ) != lookup else "" )
78- for lookup , prefix in manager_ .synonyms .items ()
79- if q_norm in lookup
76+ class PrefixLookup (BaseModel ):
77+ """A prefix lookup."""
78+
79+ lookup : str
80+ position : int
81+ complete : bool
82+
83+
84+ class PrefixSearchResult (BaseModel ):
85+ """Results from searching for a prefix."""
86+
87+ prefix : str
88+ lookups : list [PrefixLookup ]
89+
90+
91+ def _search (manager_ : Manager , query : str ) -> list [PrefixSearchResult ]:
92+ query = _norm (query )
93+ prefix_to_lookups = defaultdict (list )
94+ for lookup , prefix in manager_ .synonyms .items ():
95+ if query in lookup :
96+ start = lookup .find (query )
97+ prefix_to_lookups [prefix ].append (
98+ PrefixLookup (lookup = lookup , position = start , complete = query == lookup )
99+ )
100+ return [
101+ PrefixSearchResult (prefix = prefix , lookups = lookups )
102+ for prefix , lookups in prefix_to_lookups .items ()
80103 ]
81- return sorted (results )
82104
83105
84- def _autocomplete (manager_ : Manager , q : str , base_url : str | None = None ) -> Mapping [str , Any ]:
106+ class AutocompleteResult (BaseModel ):
107+ """Results from autocomplete."""
108+
109+ query : str
110+ success : bool
111+ reason : str
112+ results : curies .Reference | list [PrefixSearchResult ]
113+ url : str | None = None
114+ pattern : str | None = None
115+
116+
117+ def _autocomplete (manager_ : Manager , q : str , base_url : str | None = None ) -> AutocompleteResult :
85118 r"""Run the autocomplete algorithm.
86119
87120 :param manager_: A manager
@@ -130,23 +163,23 @@ def _autocomplete(manager_: Manager, q: str, base_url: str | None = None) -> Map
130163 else :
131164 reason = "searched prefix"
132165 url = None
133- return {
134- "query" : q ,
135- "results" : _search (manager_ , q ),
136- "success" : True ,
137- "reason" : reason ,
138- "url" : url ,
139- }
166+ return AutocompleteResult (
167+ query = q ,
168+ success = True ,
169+ reason = reason ,
170+ url = url ,
171+ results = _search (manager_ , q ),
172+ )
173+
140174 prefix , identifier = q .split (":" , 1 )
141175 resource = manager_ .get_resource (prefix )
142176 if resource is None :
143- return {
144- "query" : q ,
145- "prefix" : prefix ,
146- "identifier" : identifier ,
147- "success" : False ,
148- "reason" : "bad prefix" ,
149- }
177+ return AutocompleteResult (
178+ query = q ,
179+ success = False ,
180+ reason = "bad prefix" ,
181+ results = curies .Reference (prefix = prefix , identifier = identifier ),
182+ )
150183 pattern = manager_ .get_pattern (prefix )
151184 if pattern is None :
152185 success = True
@@ -162,15 +195,15 @@ def _autocomplete(manager_: Manager, q: str, base_url: str | None = None) -> Map
162195 success = False
163196 reason = "failed validation"
164197 url = None
165- return {
166- "query" : q ,
167- "prefix" : prefix ,
168- "pattern" : pattern ,
169- "identifier" : identifier ,
170- "success" : success ,
171- "reason" : reason ,
172- "url" : url ,
173- }
198+
199+ return AutocompleteResult (
200+ query = q ,
201+ success = success ,
202+ reason = reason ,
203+ url = url ,
204+ results = curies . Reference ( prefix = prefix , identifier = identifier ) ,
205+ pattern = pattern ,
206+ )
174207
175208
176209def serialize (
0 commit comments