diff --git a/CHANGELOG.md b/CHANGELOG.md index e4f61f0765..18bdc1b5b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,6 +139,7 @@ Up requirements to Django 3.2+, Twisted 21+ - Add support for `$dbref()` and `$search` when assigning an Attribute value with the `set` command. This allows assigning real objects from in-game. - Add ability to examine `/script` and `/channel` entities with `examine` command. +- Homogenize manager search methods to return querysets and not lists. ### Evennia 0.9.5 (2019-2020) diff --git a/evennia/accounts/manager.py b/evennia/accounts/manager.py index ccb00c0ce1..abe9143287 100644 --- a/evennia/accounts/manager.py +++ b/evennia/accounts/manager.py @@ -156,14 +156,17 @@ class AccountDBManager(TypedObjectManager, UserManager): (non-case-sensitive fuzzy match). typeclass (str or Typeclass, optional): Limit the search only to accounts of this typeclass. + Returns: + Queryset: A queryset (an iterable) with 0, 1 or more matches. """ dbref = self.dbref(ostring) if dbref or dbref == 0: - # bref search is always exact - matches = self.filter(id=dbref) - if matches: - return matches + # dbref search is always exact + dbref_match = self.search_dbref(dbref) + if dbref_match: + return dbref_match + query = {"username__iexact" if exact else "username__icontains": ostring} if typeclass: # we accept both strings and actual typeclasses diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index c9edf98ee7..a267d0ace9 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -237,7 +237,7 @@ class MsgManager(TypedObjectManager): always gives only one match. Returns: - Queryset: Message matches. + Queryset: Iterable with 0, 1 or more matches. """ # unique msg id @@ -420,13 +420,16 @@ class ChannelDBManager(TypedObjectManager): exact (bool, optional): Require an exact (but not case sensitive) match. + Returns: + Queryset: Iterable with 0, 1 or more matches. + """ dbref = self.dbref(ostring) if dbref: - try: - return [self.get(id=dbref)] - except self.model.DoesNotExist: - pass + dbref_match = self.search_dbref(dbref) + if dbref_match: + return dbref_match + if exact: channels = self.filter( Q(db_key__iexact=ostring) diff --git a/evennia/help/manager.py b/evennia/help/manager.py index 3d660ab114..841cc27d9f 100644 --- a/evennia/help/manager.py +++ b/evennia/help/manager.py @@ -146,6 +146,9 @@ class HelpEntryManager(TypedObjectManager): ostring (str): The help topic to look for. category (str): Limit the search to a particular help topic + Returns: + Queryset: An iterable with 0, 1 or more matches. + """ ostring = ostring.strip().lower() if help_category: diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 4fa534fb0e..724a8588a9 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -32,7 +32,7 @@ class ObjectDBManager(TypedObjectManager): Querysets or database objects). dbref (converter) - get_id (alias: dbref_search) + dbref_search get_dbref_range object_totals typeclass_search @@ -162,8 +162,8 @@ class ObjectDBManager(TypedObjectManager): typeclasses (list, optional): Python pats to restrict matches with. Returns: - matches (query): Objects fullfilling both the `attribute_name` and - `attribute_value` criterions. + Queryset: Iterable with 0, 1 or more matches fullfilling both the `attribute_name` and + `attribute_value` criterions. Notes: This uses the Attribute's PickledField to transparently search the database by matching @@ -222,6 +222,9 @@ class ObjectDBManager(TypedObjectManager): candidates (list, optional): List of objects to limit search to. typeclasses (list, optional): List of typeclass-path strings to restrict matches with + Returns: + Queryset: Iterable with 0, 1 or more matches. + """ if isinstance(property_name, str): if not property_name.startswith("db_"): @@ -234,11 +237,10 @@ class ObjectDBManager(TypedObjectManager): ) type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() try: - return list( - self.filter(cand_restriction & type_restriction & Q(**querykwargs)).order_by("id") - ) + return self.filter( + cand_restriction & type_restriction & Q(**querykwargs)).order_by("id") except exceptions.FieldError: - return [] + return self.none() except ValueError: from evennia.utils import logger @@ -246,7 +248,7 @@ class ObjectDBManager(TypedObjectManager): "The property '%s' does not support search criteria of the type %s." % (property_name, type(property_value)) ) - return [] + return self.none() def get_contents(self, location, excludeobj=None): """ @@ -258,7 +260,8 @@ class ObjectDBManager(TypedObjectManager): to exclude from the match. Returns: - contents (query): Matching contents, without excludeobj, if given. + Queryset: Iterable with 0, 1 or more matches. + """ exclude_restriction = ( Q(pk__in=[_GA(obj, "id") for obj in make_iter(excludeobj)]) if excludeobj else Q() @@ -276,17 +279,18 @@ class ObjectDBManager(TypedObjectManager): typeclasses (list): Only match objects with typeclasses having thess path strings. Returns: - matches (query): A list of matches of length 0, 1 or more. + Queryset: An iterable with 0, 1 or more matches. + """ if not isinstance(ostring, str): if hasattr(ostring, "key"): ostring = ostring.key else: - return [] + return self.none() if is_iter(candidates) and not len(candidates): # if candidates is an empty iterable there can be no matches # Exit early. - return [] + return self.none() # build query objects candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] @@ -327,10 +331,12 @@ class ObjectDBManager(TypedObjectManager): # fuzzy matching key_strings = search_candidates.values_list("db_key", flat=True).order_by("id") + match_ids = [] index_matches = string_partial_matching(key_strings, ostring, ret_index=True) if index_matches: # a match by key - return [obj for ind, obj in enumerate(search_candidates) if ind in index_matches] + match_ids = [obj.id for ind, obj in enumerate(search_candidates) + if ind in index_matches] else: # match by alias rather than by key search_candidates = search_candidates.filter( @@ -346,8 +352,10 @@ class ObjectDBManager(TypedObjectManager): index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) if index_matches: # it's possible to have multiple matches to the same Object, we must weed those out - return list({alias_candidates[ind] for ind in index_matches}) - return [] + match_ids = [alias_candidates[ind].id for ind in index_matches] + # TODO - not ideal to have to do a second lookup here, but we want to return a queryset + # rather than a list ... maybe the above queries can be improved. + return self.filter(id__in=match_ids) # main search methods and helper functions @@ -422,7 +430,7 @@ class ObjectDBManager(TypedObjectManager): ) if not searchdata and searchdata != 0: - return [] + return self.none() if typeclass: # typeclass may also be a list @@ -450,10 +458,11 @@ class ObjectDBManager(TypedObjectManager): # Easiest case - dbref matching (always exact) dbref_match = self.dbref_search(dbref) if dbref_match: - if not candidates or dbref_match in candidates: - return [dbref_match] + dmatch = dbref_match[0] + if not candidates or dmatch in candidates: + return dbref_match else: - return [] + return self.none() # Search through all possibilities. match_number = None @@ -478,15 +487,16 @@ class ObjectDBManager(TypedObjectManager): # this indicates trying to get a single match with a match-number # targeting some higher-number match (like 2-box when there is only # one box in the room). This leads to a no-match. - matches = [] + matches = self.none() elif len(matches) > 1 and match_number is not None: # multiple matches, but a number was given to separate them if 0 <= match_number < len(matches): - # limit to one match - matches = [matches[match_number]] + # limit to one match (we still want a queryset back) + # TODO: Can we do this some other way and avoid a second lookup? + matches = self.filter(id=matches[match_number].id) else: # a number was given outside of range. This means a no-match. - matches = [] + matches = self.none() # return a list (possibly empty) return matches diff --git a/evennia/scripts/manager.py b/evennia/scripts/manager.py index 900fdd45da..8c3307e90a 100644 --- a/evennia/scripts/manager.py +++ b/evennia/scripts/manager.py @@ -28,7 +28,7 @@ class ScriptDBManager(TypedObjectManager): Querysets or database objects). dbref (converter) - get_id (or dbref_search) + dbref_search get_dbref_range object_totals typeclass_search @@ -138,6 +138,9 @@ class ScriptDBManager(TypedObjectManager): on a timer. typeclass (class or str): Typeclass or path to typeclass. + Returns: + Queryset: An iterable with 0, 1 or more results. + """ ostring = ostring.strip() @@ -146,10 +149,10 @@ class ScriptDBManager(TypedObjectManager): if dbref: # this is a dbref, try to find the script directly dbref_match = self.dbref_search(dbref) - if dbref_match and not ( - (obj and obj != dbref_match.obj) or (only_timed and dbref_match.interval) - ): - return [dbref_match] + if dbref_match: + dmatch = dbref_match[0] + if not (obj and obj != dmatch.obj) or (only_timed and dmatch.interval): + return dbref_match if typeclass: if callable(typeclass): diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 8d461d7dc9..dc4060ff17 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -464,10 +464,13 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): dbref (str or int): The id to search for. Returns: - object (TypedObject): The matched object. + Queryset: Queryset with 0 or 1 match. """ - return self.get_id(dbref) + dbref = self.dbref(dbref, reqhash=False) + if dbref: + return self.filter(id=dbref) + return self.none() def get_dbref_range(self, min_dbref=None, max_dbref=None): """