Skip to content

Commit

Permalink
dbio.wsgi: carve out endpoint for adv search
Browse files Browse the repository at this point in the history
  • Loading branch information
RayPlante committed Mar 15, 2024
1 parent 8a1a7d1 commit 9c33dc7
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 26 deletions.
8 changes: 8 additions & 0 deletions python/nistoar/midas/dap/service/mds3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2381,6 +2381,14 @@ def _select_records(self, perms) -> Iterator[ProjectRecord]:
for rec in self._dbcli.select_records(perms):
yield to_DAPRec(rec, self._fmcli)

def _adv_selected_records(self, filter, perms) -> Iterator[ProjectRecord]:
"""
submit the advanced search query in a project-specific way. This implementation passes
the query directly to the generic DBClient instance.
:return: a generator that iterates through the matched records
"""
for rec in self._dbcli.select_constraint_records(filter, perms):
yield to_DAPRec(rec, self._fmcli)



86 changes: 60 additions & 26 deletions python/nistoar/midas/dbio/wsgi/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ class ProjectSelectionHandler(ProjectRecordHandler):
"""

def __init__(self, service: ProjectService, subapp: SubApp, wsgienv: dict, start_resp: Callable,
who: PubAgent, config: dict=None, log: Logger=None):
who: PubAgent, iftype: str=None, config: dict=None, log: Logger=None):
"""
Initialize this record request handler with the request particulars. This constructor is called
by the webs service SubApp in charge of the project record interface.
Expand All @@ -411,22 +411,28 @@ def __init__(self, service: ProjectService, subapp: SubApp, wsgienv: dict, start
:param dict wsgienv: the WSGI request context dictionary
:param Callable start_resp: the WSGI start-response function used to send the response
:param PubAgent who: the authenticated user making the request.
:param str iftype: a name for the search interface type that is requested. Recognized values
include ":selected" which supports Mongo-like filter syntax. If not provided,
a simple query-parameter-based filter syntax is used.
:param dict config: the handler's configuration; if not provided, the inherited constructor
will extract the configuration from `subapp`. Normally, the constructor
is called without this parameter.
:param Logger log: the logger to use within this handler; if not provided (typical), the
logger attached to the SubApp will be used.
"""
super(ProjectSelectionHandler, self).__init__(service, subapp, wsgienv, start_resp, who, "",
if iftype is None:
iftype = ""
super(ProjectSelectionHandler, self).__init__(service, subapp, wsgienv, start_resp, who, iftype,
config, log)

def do_OPTIONS(self, path):
return self.send_options(["GET", "POST"])

def _select_records(self, perms) -> Iterator[ProjectRecord]:
"""
submit a search query in a project specific way. This implementation passes the query
directly to the generic DBClient instance.
submit a search query in a project specific way. This method is provided as a
hook to subclasses that may need to specialize the search strategy or manipulate the results.
This implementation passes the query directly to the generic DBClient instance.
:return: a generator that iterates through the matched records
"""
return self._dbcli.select_records(perms)
Expand All @@ -447,27 +453,10 @@ def do_GET(self, path, ashead=False):
perms = dbio.ACLs.OWN

# sort the results by the best permission type permitted
selected = OrderedDict()
for rec in self._select_records(perms):
maxperm = ''
if rec.owner == self._dbcli.user_id:
maxperm = "owner"
elif rec.authorized(dbio.ACLs.ADMIN):
maxperm = dbio.ACLs.ADMIN
elif rec.authorized(dbio.ACLs.WRITE):
maxperm = dbio.ACLs.WRITE
else:
maxperm = dbio.ACLs.READ

if maxperm not in selected:
selected[maxperm] = []
selected[maxperm].append(rec)

# order the matched records based on best permissions
out = []
for perm in ["owner", dbio.ACLs.ADMIN, dbio.ACLs.WRITE, dbio.ACLs.READ]:
for rec in selected.get(perm, []):
out.append(rec.to_dict())
sortd = SortByPerm()
for rec in self._selected_records(perms):
sortd.add_record(rec)
out = [rec.to_dict() for rec in sortd.sorted()]

return self.send_json(out, ashead=ashead)

Expand All @@ -476,10 +465,52 @@ def do_POST(self, path):
create a new project record given some initial data
"""
try:
newdata = self.get_json_body()
input = self.get_json_body()
except self.FatalError as ex:
return self.send_fatal_error(ex)

path = path.rstrip('/')
if not path:
return self.create_record(input)
elif path == ':selected':
return self.adv_select_records(input)
else:
return send_error_resp(400, "Selection resource not found")

def adv_select_records(self, input: Mapping):
"""
submit a record search
:param dict filter: the search constraints for the search.
"""
filter = input.get("filter", {})
perms = input.get("permissions", [])
if not perms:
perms = [ dbio.ACLs.OWN ]

# sort the results by the best permission type permitted
sortd = SortByPerm()
for rec in self._adv_select_records(filter, perms):
sortd.add_record(rec)
out = [rec.to_dict() for rec in sortd.sorted()]

return self.send_json(out, ashead=ashead)

def _adv_select_records(self, filter, perms) -> Iterator[ProjectRecord]:
"""
submit the advanced search query in a project-specific way. This method is provided as a
hook to subclasses that may need to specialize the search strategy or manipulate the results.
This base implementation passes the query directly to the generic DBClient instance.
:return: a generator that iterates through the matched records
"""
return self._dbcli.select_contraint_records(filter, perms)

def create_record(self, newdata: Mapping):
"""
create a new record
:param dict newdata: the initial data for the record. This data is expected to conform to
the DBIO record schema (with ``data`` and ``meta`` content appropriate
for the project type).
"""
if not newdata.get('name'):
return self.send_error_resp(400, "Bad POST input", "No mneumonic name provided")

Expand Down Expand Up @@ -895,6 +926,9 @@ def create_handler(self, env: dict, start_resp: Callable, path: str, who: PubAge
if not idattrpart[0]:
# path is empty: this is used to list all available projects or create a new one
return self._selection_handler(service, self, env, start_resp, who)
elif idattrpart[0][0] == ":":
# path starts with ":" (e.g. ":selected"); this indicates a search resource
return self._selection_handler(service, self, env, start_resp, who, idattrpart[0])
else:
# path is just an ID:
return self._update_handler(service, self, env, start_resp, who, idattrpart[0])
Expand Down

0 comments on commit 9c33dc7

Please sign in to comment.