def nickreplace(self, raw_string, categories=("inputline", "channel"), include_player=True): """ Apply nick replacement of entries in raw_string with nick replacement. Args: raw_string (str): The string in which to perform nick replacement. categories (tuple, optional): Replacement categories in which to perform the replacement, such as "inputline", "channel" etc. include_player (bool, optional): Also include replacement with nicks stored on the Player level. kwargs (any, optional): Not used. Returns: string (str): A string with matching keys replaced with their nick equivalents. """ raw_string obj_nicks, player_nicks = [], [] for category in make_iter(categories): obj_nicks.extend([n for n in make_iter(self.get(category=category, return_obj=True)) if n]) if include_player and self.obj.has_player: for category in make_iter(categories): player_nicks.extend([n for n in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) if n]) for nick in obj_nicks + player_nicks: # make a case-insensitive match here match = re.match(re.escape(nick.db_key), raw_string, re.IGNORECASE) if match: raw_string = raw_string.replace(match.group(), nick.db_strvalue, 1) break return raw_string
def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None): """ Args: ostring (str): A search criterion. exact (bool, optional): Require exact match of ostring (still case-insensitive). If `False`, will do fuzzy matching using `evennia.utils.utils.string_partial_matching` algorithm. candidates (list): Only match among these candidates. typeclasses (list): Only match objects with typeclasses having thess path strings. Returns: matches (list): A list of matches of length 0, 1 or more. """ if not isinstance(ostring, basestring): if hasattr(ostring, "key"): ostring = ostring.key else: return [] if is_iter(candidates) and not len(candidates): # if candidates is an empty iterable there can be no matches # Exit early. return [] # build query objects candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] cand_restriction = candidates != None and Q(pk__in=candidates_id) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() if exact: # exact match - do direct search return self.filter(cand_restriction & type_restriction & (Q(db_key__iexact=ostring) | Q(db_tags__db_key__iexact=ostring) & Q(db_tags__db_tagtype__iexact="alias"))).distinct() elif candidates: # fuzzy with candidates search_candidates = self.filter(cand_restriction & type_restriction) else: # fuzzy without supplied candidates - we select our own candidates search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct() # fuzzy matching key_strings = search_candidates.values_list("db_key", flat=True).order_by("id") 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] else: # match by alias rather than by key search_candidates = search_candidates.filter(db_tags__db_tagtype__iexact="alias", db_tags__db_key__icontains=ostring) alias_strings = [] alias_candidates = [] #TODO create the alias_strings and alias_candidates lists more effiently? for candidate in search_candidates: for alias in candidate.aliases.all(): alias_strings.append(alias) alias_candidates.append(candidate) index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) if index_matches: return [alias_candidates[ind] for ind in index_matches] return []
def get_objs_with_db_property_value(self, property_name, property_value, candidates=None, typeclasses=None): """ Get objects with a specific field name and value. Args: property_name (str): Field name to search for. property_value (any): Value required for field with `property_name` to have. candidates (list, optional): List of objects to limit search to. typeclasses (list, optional): List of typeclass-path strings to restrict matches with """ if isinstance(property_value, basestring): property_value = to_unicode(property_value) if isinstance(property_name, basestring): if not property_name.startswith("db_"): property_name = "db_%s" % property_name querykwargs = {property_name: property_value} cand_restriction = ( candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() ) 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))) except exceptions.FieldError: return [] except ValueError: from evennia.utils import logger logger.log_errmsg( "The property '%s' does not support search criteria of the type %s." % (property_name, type(property_value)) ) return []
def func(self): "Implement function" caller = self.caller # all channels we have available to listen to channels = [chan for chan in ChannelDB.objects.get_all_channels() if chan.access(caller, "listen")] if not channels: self.msg("No channels available.") return # all channel we are already subscribed to subs = ChannelDB.objects.get_subscriptions(caller) if self.cmdstring == "comlist": # just display the subscribed channels with no extra info comtable = evtable.EvTable( "{wchannel{n", "{wmy aliases{n", "{wdescription{n", align="l", maxwidth=_DEFAULT_WIDTH ) # comtable = prettytable.PrettyTable(["{wchannel", "{wmy aliases", "{wdescription"]) for chan in subs: clower = chan.key.lower() nicks = caller.nicks.get(category="channel") comtable.add_row( *[ "%s%s" % (chan.key, chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or ""), "%s".join(nick for nick in make_iter(nicks) if nick and nick.lower() == clower), chan.db.desc, ] ) caller.msg( "\n{wChannel subscriptions{n (use {w@channels{n to list all, {waddcom{n/{wdelcom{n to sub/unsub):{n\n%s" % comtable ) else: # full listing (of channels caller is able to listen to) comtable = evtable.EvTable( "{wsub{n", "{wchannel{n", "{wmy aliases{n", "{wlocks{n", "{wdescription{n", maxwidth=_DEFAULT_WIDTH ) # comtable = prettytable.PrettyTable(["{wsub", "{wchannel", "{wmy aliases", "{wlocks", "{wdescription"]) for chan in channels: clower = chan.key.lower() nicks = caller.nicks.get(category="channel") nicks = nicks or [] comtable.add_row( *[ chan in subs and "{gYes{n" or "{rNo{n", "%s%s" % (chan.key, chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or ""), "%s".join(nick for nick in make_iter(nicks) if nick.lower() == clower), str(chan.locks), chan.db.desc, ] ) caller.msg( "\n{wAvailable channels{n (use {wcomlist{n,{waddcom{n and {wdelcom{n to manage subscriptions):\n%s" % comtable )
def __init__( self, senders=None, receivers=None, channels=None, message="", header="", type="", lockstring="", hide_from=None ): self.senders = senders and make_iter(senders) or [] self.receivers = receivers and make_iter(receivers) or [] self.channels = channels and make_iter(channels) or [] self.type = type self.header = header self.message = message self.lock_storage = lockstring self.hide_from = hide_from and make_iter(hide_from) or [] self.date_sent = timezone.now()
def batch_add(self, key, value, category=None, lockstring="", strattr=False, accessing_obj=None, default_access=True): """ Batch-version of `add()`. This is more efficient than repeat-calling add. `key` and `value` must be sequences of the same length, each representing a key-value pair. """ if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access): # check create access return if self._cache is None: self._recache() if not key: return keys, values= make_iter(key), make_iter(value) if len(keys) != len(values): raise RuntimeError("AttributeHandler.add(): key and value of different length: %s vs %s" % key, value) category = category.strip().lower() if category is not None else None new_attrobjs = [] for ikey, keystr in enumerate(keys): keystr = keystr.strip().lower() new_value = values[ikey] cachekey = "%s-%s" % (keystr, category) attr_obj = self._cache.get(cachekey) if attr_obj: # update an existing attribute object if strattr: # store as a simple string (will not notify OOB handlers) attr_obj.db_strvalue = new_value attr_obj.save(update_fields=["db_strvalue"]) else: # store normally (this will also notify OOB handlers) attr_obj.value = new_value else: # create a new Attribute (no OOB handlers can be notified) kwargs = {"db_key" : keystr, "db_category" : category, "db_attrtype" : self._attrtype, "db_value" : None if strattr else to_pickle(new_value), "db_strvalue" : value if strattr else None} new_attr = Attribute(**kwargs) new_attr.save() new_attrobjs.append(new_attr) if new_attrobjs: # Add new objects to m2m field all at once getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs) self._recache()
def func(self): """Implement function""" caller = self.caller # all channels we have available to listen to channels = [chan for chan in ChannelDB.objects.get_all_channels() if chan.access(caller, 'listen')] if not channels: self.msg("No channels available.") return # all channel we are already subscribed to subs = ChannelDB.objects.get_subscriptions(caller) if self.cmdstring == "comlist": # just display the subscribed channels with no extra info comtable = evtable.EvTable("|wchannel|n", "|wmy aliases|n", "|wdescription|n", align="l", maxwidth=_DEFAULT_WIDTH) for chan in subs: clower = chan.key.lower() nicks = caller.nicks.get(category="channel", return_obj=True) comtable.add_row(*["%s%s" % (chan.key, chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or ""), "%s" % ",".join(nick.db_key for nick in make_iter(nicks) if nick and nick.value[3].lower() == clower), chan.db.desc]) self.msg("\n|wChannel subscriptions|n (use |w@channels|n to list all," " |waddcom|n/|wdelcom|n to sub/unsub):|n\n%s" % comtable) else: # full listing (of channels caller is able to listen to) comtable = evtable.EvTable("|wsub|n", "|wchannel|n", "|wmy aliases|n", "|wlocks|n", "|wdescription|n", maxwidth=_DEFAULT_WIDTH) for chan in channels: clower = chan.key.lower() nicks = caller.nicks.get(category="channel", return_obj=True) nicks = nicks or [] if chan not in subs: substatus = "|rNo|n" elif caller in chan.mutelist: substatus = "|rMuted|n" else: substatus = "|gYes|n" comtable.add_row(*[substatus, "%s%s" % (chan.key, chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or ""), "%s" % ",".join(nick.db_key for nick in make_iter(nicks) if nick.value[3].lower() == clower), str(chan.locks), chan.db.desc]) comtable.reformat_column(0, width=9) comtable.reformat_column(3, width=14) self.msg("\n|wAvailable channels|n (use |wcomlist|n,|waddcom|n and |wdelcom|n" " to manage subscriptions):\n%s" % comtable)
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ Evennia -> User This is the main route for sending data back to the user from the server. Args: text (str, optional): text data to send from_obj (Object or Account or list, optional): Object sending. If given, its at_msg_send() hook will be called. If iterable, call on all entities. session (Session or list, optional): Session object or a list of Sessions to receive this send. If given, overrules the default send behavior for the current MULTISESSION_MODE. options (list): Protocol-specific options. Passed on to the protocol. Kwargs: any (dict): All other keywords are passed on to the protocol. """ if from_obj: # call hook for obj in make_iter(from_obj): try: obj.at_msg_send(text=text, to_obj=self, **kwargs) except Exception: # this may not be assigned. logger.log_trace() try: if not self.at_msg_receive(text=text, **kwargs): # abort message to this account return except Exception: # this may not be assigned. pass kwargs["options"] = options if text is not None: if not (isinstance(text, basestring) or isinstance(text, tuple)): # sanitize text before sending across the wire try: text = to_str(text, force_string=True) except Exception: text = repr(text) kwargs['text'] = text # session relay sessions = make_iter(session) if session else self.sessions.all() for session in sessions: session.data_out(**kwargs)
def has(self, key, category=None): """ Checks if the given Attribute (or list of Attributes) exists on the object. If an iterable is given, returns list of booleans. """ if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: self._recache() key = [k.strip().lower() for k in make_iter(key) if k] category = category.strip().lower() if category is not None else None searchkeys = ["%s-%s" % (k, category) for k in make_iter(key)] ret = [self._cache.get(skey) for skey in searchkeys if skey in self._cache] return ret[0] if len(ret) == 1 else ret
def nickreplace(self, raw_string, categories=("inputline", "channel"), include_player=True): "Replace entries in raw_string with nick replacement" raw_string obj_nicks, player_nicks = [], [] for category in make_iter(categories): obj_nicks.extend([n for n in make_iter(self.get(category=category, return_obj=True)) if n]) if include_player and self.obj.has_player: for category in make_iter(categories): player_nicks.extend([n for n in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) if n]) for nick in obj_nicks + player_nicks: # make a case-insensitive match here match = re.match(re.escape(nick.db_key), raw_string, re.IGNORECASE) if match: raw_string = raw_string.replace(match.group(), nick.db_strvalue, 1) break return raw_string
def get(self, exclude=None): """ Return the contents of the cache. Args: exclude (Object or list of Object): object(s) to ignore Returns: objects (list): the Objects inside this location """ if exclude: pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]] else: pks = self._pkcache try: return [self._idcache[pk] for pk in pks] except KeyError: # this can happen if the idmapper cache was cleared for an object # in the contents cache. If so we need to re-initialize and try again. self.init() try: return [self._idcache[pk] for pk in pks] except KeyError: # this means an actual failure of caching. Return real database match. logger.log_err("contents cache failed for %s." % (self.obj.key)) return list(ObjectDB.objects.filter(db_location=self.obj))
def get(self, key=None, default=None, category=None, return_tagobj=False): """ Get the tag for the given key or list of tags. Args: key (str or list): The tag or tags to retrieve. default (any, optional): The value to return in case of no match. category (str, optional): The Tag category to limit the request to. Note that `None` is the valid, default category. return_tagobj (bool, optional): Return the Tag object itself instead of a string representation of the Tag. Returns: tags (str, TagObject or list): The matches, either string representations of the tags or the Tag objects themselves depending on `return_tagobj`. """ ret = [] for keystr in make_iter(key): # note - the _getcache call removes case sensitivity for us ret.extend([tag if return_tagobj else to_str(tag.db_key) for tag in self._getcache(keystr, category)]) return ret[0] if len(ret) == 1 else (ret if ret else default)
def remove(self, key, raise_exception=False, category=None, accessing_obj=None, default_access=True): """ Remove attribute or a list of attributes from object. Args: key (str): An Attribute key to remove. raise_exception (bool, optional): If set, not finding the Attribute to delete will raise an exception instead of just quietly failing. category (str, optional): The category within which to remove the Attribute. accessing_obj (object, optional): An object to check against the `attredit` lock. If not given, the check will be skipped. default_access (bool, optional): The fallback access to grant if `accessing_obj` is given but there is no `attredit` lock set on the Attribute in question. Raises: AttributeError: If `raise_exception` is set and no matching Attribute was found matching `key`. """ if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: self._recache() key = [k.strip().lower() for k in make_iter(key) if k] category = category.strip().lower() if category is not None else None for searchstr in ("%s-%s" % (k, category) for k in key): attr_obj = self._cache.get(searchstr) if attr_obj: if not (accessing_obj and not attr_obj.access(accessing_obj, self._attredit, default=default_access)): attr_obj.delete() elif not attr_obj and raise_exception: raise AttributeError self._recache()
def remove_receiver(self, obj): "Remove a sender or a list of senders" for o in make_iter(obj): try: self.senders.remove(o) except ValueError: pass # nothing to remove
def _get_prototype(inprot, protparents, uninherited=None, _workprot=None): """ Recursively traverse a prototype dictionary, including multiple inheritance. Use validate_prototype before this, we don't check for infinite recursion here. Args: inprot (dict): Prototype dict (the individual prototype, with no inheritance included). protparents (dict): Available protparents, keyed by prototype_key. uninherited (dict): Parts of prototype to not inherit. _workprot (dict, optional): Work dict for the recursive algorithm. """ _workprot = {} if _workprot is None else _workprot if "prototype_parent" in inprot: # move backwards through the inheritance for prototype in make_iter(inprot["prototype_parent"]): # Build the prot dictionary in reverse order, overloading new_prot = _get_prototype(protparents.get(prototype.lower(), {}), protparents, _workprot=_workprot) _workprot.update(new_prot) # the inprot represents a higher level (a child prot), which should override parents _workprot.update(inprot) if uninherited: # put back the parts that should not be inherited _workprot.update(uninherited) _workprot.pop("prototype_parent", None) # we don't need this for spawning return _workprot
def add(self, entity): """ Subscribe an entity to this channel. Args: entity (Player, Object or list): The entity or list of entities to subscribe to this channel. Note: No access-checking is done here, this must have been done before calling this method. Also no hooks will be called. """ global _CHANNELHANDLER if not _CHANNELHANDLER: from evennia.comms.channelhandler import CHANNEL_HANDLER as _CHANNELHANDLER for subscriber in make_iter(entity): if subscriber: clsname = subscriber.__dbclass__.__name__ # chooses the right type if clsname == "ObjectDB": self.obj.db_object_subscriptions.add(subscriber) elif clsname == "PlayerDB": self.obj.db_subscriptions.add(subscriber) _CHANNELHANDLER.cached_cmdsets.pop(subscriber, None) self._recache()
def create_tag(self, key=None, category=None, data=None, tagtype=None): """ Create a new Tag of the base type associated with this typedobject. This makes sure to create case-insensitive tags. If the exact same tag configuration (key+category+tagtype) exists on the model, a new tag will not be created, but an old one returned. A data keyword is not part of the uniqueness of the tag and setting one on an existing tag will overwrite the old data field. """ data = str(data) if data is not None else None # try to get old tag tag = self.get_tag(key=key, category=category, tagtype=tagtype, global_search=True) if tag and data is not None: # overload data on tag tag.db_data = data tag.save() elif not tag: # create a new tag global _Tag if not _Tag: from evennia.typeclasses.models import Tag as _Tag tag = _Tag.objects.create( db_key=key.strip().lower() if key is not None else None, db_category=category.strip().lower() if category and key is not None else None, db_data=data, db_tagtype=tagtype.strip().lower() if tagtype is not None else None) tag.save() return make_iter(tag)[0]
def get(self, key=None, category="inputline", return_tuple=False, **kwargs): """ Get the replacement value matching the given key and category Args: key (str or list, optional): the attribute identifier or multiple attributes to get. if a list of keys, the method will return a list. category (str, optional): the category within which to retrieve the nick. The "inputline" means replacing data sent by the user. return_tuple (bool, optional): return the full nick tuple rather than just the replacement. For non-template nicks this is just a string. kwargs (any, optional): These are passed on to `AttributeHandler.get`. """ if return_tuple or "return_obj" in kwargs: return super(NickHandler, self).get(key=key, category=category, **kwargs) else: retval = super(NickHandler, self).get(key=key, category=category, **kwargs) if retval: return retval[3] if isinstance(retval, tuple) else \ [tup[3] for tup in make_iter(retval)] return None
def init_spawn_value(value, validator=None): """ Analyze the prototype value and produce a value useful at the point of spawning. Args: value (any): This can be: callable - will be called as callable() (callable, (args,)) - will be called as callable(*args) other - will be assigned depending on the variable type validator (callable, optional): If given, this will be called with the value to check and guarantee the outcome is of a given type. Returns: any (any): The (potentially pre-processed value to use for this prototype key) """ value = protfunc_parser(value) validator = validator if validator else lambda o: o if callable(value): return validator(value()) elif value and is_iter(value) and callable(value[0]): # a structure (callable, (args, )) args = value[1:] return validator(value[0](*make_iter(args))) else: return validator(value)
def unpuppet_object(self, sessid): """ Disengage control over an object Args: sessid(int): the session id to disengage Raises: RuntimeError with message about error. """ if _MULTISESSION_MODE == 1: sessions = self.get_all_sessions() else: sessions = self.get_session(sessid) if not sessions: raise RuntimeError("No session was found.") for session in make_iter(sessions): obj = session.puppet or None if not obj: raise RuntimeError("No puppet was found to disconnect from.") elif obj: # do the disconnect, but only if we are the last session to puppet obj.at_pre_unpuppet() obj.sessid.remove(session.sessid) if not obj.sessid.count(): del obj.player obj.at_post_unpuppet(self, sessid=sessid) # Just to be sure we're always clear. session.puppet = None session.puid = None
def add(self, tag=None, category=None, data=None): """ Add a new tag to the handler. Args: tag (str or list): The name of the tag to add. If a list, add several Tags. category (str, optional): Category of Tag. `None` is the default category. data (str, optional): Info text about the tag(s) added. This can not be used to store object-unique info but only eventual info about the text itself. Notes: If the tag + category combination matches an already existing Tag object, this will be re-used and no new Tag will be created. """ if not tag: return for tagstr in make_iter(tag): if not tagstr: continue tagstr = tagstr.strip().lower() category = category.strip().lower() if category is not None else None data = str(data) if data is not None else None # this will only create tag if no matches existed beforehand (it # will overload data on an existing tag since that is not # considered part of making the tag unique) tagobj = self.obj.__class__.objects.create_tag( key=tagstr, category=category, data=data, tagtype=self._tagtype ) getattr(self.obj, self._m2m_fieldname).add(tagobj) self._setcache(tagstr, category, tagobj)
def get(self, key=None, default=None, category=None, return_tagobj=False, return_list=False): """ Get the tag for the given key, category or combination of the two. Args: key (str or list, optional): The tag or tags to retrieve. default (any, optional): The value to return in case of no match. category (str, optional): The Tag category to limit the request to. Note that `None` is the valid, default category. If no `key` is given, all tags of this category will be returned. return_tagobj (bool, optional): Return the Tag object itself instead of a string representation of the Tag. return_list (bool, optional): Always return a list, regardless of number of matches. Returns: tags (list): The matches, either string representations of the tags or the Tag objects themselves depending on `return_tagobj`. If 'default' is set, this will be a list with the default value as its only element. """ ret = [] for keystr in make_iter(key): # note - the _getcache call removes case sensitivity for us ret.extend([tag if return_tagobj else to_str(tag.db_key) for tag in self._getcache(keystr, category)]) if return_list: return ret if ret else [default] if default is not None else [] return ret[0] if len(ret) == 1 else (ret if ret else default)
def remove(self, key, category=None): """ Remove a tag from the handler based ond key and category. Args: key (str or list): The tag or tags to retrieve. category (str, optional): The Tag category to limit the request to. Note that `None` is the valid, default category. """ for key in make_iter(key): if not (key or key.strip()): # we don't allow empty tags continue tagstr = key.strip().lower() category = category.strip().lower() if category else category # This does not delete the tag object itself. Maybe it should do # that when no objects reference the tag anymore (but how to check)? # For now, tags are never deleted, only their connection to objects. tagobj = getattr(self.obj, self._m2m_fieldname).filter(db_key=tagstr, db_category=category, db_model=self._model, db_tagtype=self._tagtype) if tagobj: getattr(self.obj, self._m2m_fieldname).remove(tagobj[0]) self._delcache(key, category)
def msg(self, text=None, from_obj=None, session=None, **kwargs): """ Evennia -> User This is the main route for sending data back to the user from the server. Args: text (str, optional): text data to send from_obj (Object or Player, optional): Object sending. If given, its at_msg_send() hook will be called. session (Session or list, optional): Session object or a list of Sessions to receive this send. If given, overrules the default send behavior for the current MULTISESSION_MODE. Notes: All other keywords are passed on to the protocol. """ text = to_str(text, force_string=True) if text else "" if from_obj: # call hook try: from_obj.at_msg_send(text=text, to_obj=self, **kwargs) except Exception: pass # session relay sessions = make_iter(session) if session else self.sessions.all() for session in sessions: session.msg(text=text, **kwargs)
def __cmdset_storage_set(self, value): """ Setter. Allows for self.name = value. Stores as a comma-separated string. """ _SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value))) _GA(self, "save")()
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None): """ Get all objects having the given attrname set to the given value. Args: attribute_name (str): Attribute key to search for. attribute_value (str): Attribute value to search for. candidates (list, optional): Candidate objects to limit search to. typeclasses (list, optional): Python pats to restrict matches with. Returns: matches (list): Objects fullfilling both the `attribute_name` and `attribute_value` criterions. Notes: This uses the Attribute's PickledField to transparently search the database by matching the internal representation. This is reasonably effective but since Attribute values cannot be indexed, searching by Attribute key is to be preferred whenever possible. """ cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() ## This doesn't work if attribute_value is an object. Workaround below if isinstance(attribute_value, (basestring, int, float, bool)): return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value)) else: # We have to loop for safety since the referenced lookup gives deepcopy error if attribute value is an object. global _ATTR if not _ATTR: from evennia.typeclasses.models import Attribute as _ATTR cands = list(self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name))) results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands, db_value=attribute_value)] return chain(*results)
def __channels_set(self, value): """ Setter. Allows for self.channels = value. Requires a channel to be added. """ for val in (v for v in make_iter(value) if v): self.db_receivers_channels.add(val)
def get_object_with_player(self, ostring, exact=True, candidates=None): """ Search for an object based on its player's name or dbref. Args: ostring (str or int): Search criterion or dbref. Searching for a player is sometimes initiated by appending an `*` to the beginning of the search criterion (e.g. in local_and_global_search). This is stripped here. exact (bool, optional): Require an exact player match. candidates (list, optional): Only search among this list of possible object candidates. Return: match (Object or list): One or more matching results. """ ostring = to_unicode(ostring).lstrip('*') # simplest case - search by dbref dbref = self.dbref(ostring) if dbref: return dbref # not a dbref. Search by name. cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() if exact: return self.filter(cand_restriction & Q(db_player__username__iexact=ostring)) else: # fuzzy matching ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)).values_list("db_key", flat=True) if candidates: index_matches = string_partial_matching(ply_cands, ostring, ret_index=True) return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches] else: return string_partial_matching(ply_cands, ostring, ret_index=False)
def batch_add(self, *args): """ Batch-add tags from a list of tuples. Args: tuples (tuple or str): Any number of `tagstr` keys, `(keystr, category)` or `(keystr, category, data)` tuples. Notes: This will generate a mimimal number of self.add calls, based on the number of categories involved (including `None`) (data is not unique and may be overwritten by the content of a latter tuple with the same category). """ keys = defaultdict(list) data = {} for tup in args: tup = make_iter(tup) nlen = len(tup) if nlen == 1: # just a key keys[None].append(tup[0]) elif nlen == 2: keys[tup[1]].append(tup[0]) else: keys[tup[1]].append(tup[0]) data[tup[1]] = tup[2] # overwrite previous for category, key in keys.iteritems(): self.add(tag=key, category=category, data=data.get(category, None))
def func(self): "Create the nickname" caller = self.caller switches = self.switches nicksinputline = caller.nicks.get(category="inputline", return_obj=True) nicksobjects = caller.nicks.get(category="object", return_obj=True) nicksplayers = caller.nicks.get(category="player", return_obj=True) if "list" in switches: if not nicksinputline and not nicksobjects and not nicksplayers: string = "{wNo nicks defined.{n" else: table = prettytable.PrettyTable(["{wNickType", "{wNickname", "{wTranslates-to"]) for nicks in (nicksinputline, nicksobjects, nicksplayers): for nick in utils.make_iter(nicks): table.add_row([nick.db_category, nick.db_key, nick.db_strvalue]) string = "{wDefined Nicks:{n\n%s" % table caller.msg(string) return if "clearall" in switches: caller.nicks.clear() caller.msg("Cleared all aliases.") return if not self.args or not self.lhs: caller.msg("Usage: nick[/switches] nickname = [realname]") return nick = self.lhs real = self.rhs if real == nick: caller.msg("No point in setting nick same as the string to replace...") return # check so we have a suitable nick type if not any(True for switch in switches if switch in ("object", "player", "inputline")): switches = ["inputline"] string = "" for switch in switches: oldnick = caller.nicks.get(key=nick, category=switch) if not real: if "=" in self.args: if oldnick: # clear the alias string += "\nNick cleared: '{w%s{n' (-> '{w%s{n')." % (nick, oldnick) caller.nicks.remove(nick, category=switch) else: string += "\nNo nick '%s' found, so it could not be removed." % nick else: string += "\nNick: '{w%s{n'{n -> '{w%s{n'." % (nick, oldnick) else: # creating new nick if oldnick: string += "\nNick '{w%s{n' reset from '{w%s{n' to '{w%s{n'." % (nick, oldnick, real) else: string += "\nNick set: '{w%s{n' -> '{w%s{n'." % (nick, real) caller.nicks.add(nick, real, category=switch) caller.msg(string)
def add(self, tag=None, category=None, data=None): """ Add a new tag to the handler. Args: tag (str or list): The name of the tag to add. If a list, add several Tags. category (str, optional): Category of Tag. `None` is the default category. data (str, optional): Info text about the tag(s) added. This can not be used to store object-unique info but only eventual info about the text itself. Notes: If the tag + category combination matches an already existing Tag object, this will be re-used and no new Tag will be created. """ if not tag: return for tagstr in make_iter(tag): if not tagstr: continue tagstr = tagstr.strip().lower() category = category.strip().lower( ) if category is not None else None data = str(data) if data is not None else None # this will only create tag if no matches existed beforehand (it # will overload data on an existing tag since that is not # considered part of making the tag unique) tagobj = self.obj.__class__.objects.create_tag( key=tagstr, category=category, data=data, tagtype=self._tagtype) getattr(self.obj, self._m2m_fieldname).add(tagobj) self._setcache(tagstr, category, tagobj)
def get(self, exclude=None): """ Return the contents of the cache. Args: exclude (Object or list of Object): object(s) to ignore Returns: objects (list): the Objects inside this location """ pks = self._pkcache.keys() if exclude: pks = [ pk for pk in pks if pk not in [excl.pk for excl in make_iter(exclude)] ] try: return [self._idcache[pk] for pk in pks] except KeyError: # this can happen if the idmapper cache was cleared for an object # in the contents cache. If so we need to re-initialize and try again. self.init() return self.get(exclude=exclude)
def func(self): "delete the character" player = self.player if not self.args: self.msg("Usage: @chardelete <charactername>") return # use the playable_characters list to search match = [char for char in utils.make_iter(player.db._playable_characters) if char.key.lower() == self.args.lower()] if not match: self.msg("You have no such character to delete.") return elif len(match) > 1: self.msg("Aborting - there are two characters with the same name. Ask an admin to delete the right one.") return else: # one match from evennia.utils.evmenu import get_input def _callback(caller, prompt, result): if result.lower() == "yes": # only take action delobj = caller.ndb._char_to_delete key = delobj.key caller.db._playable_characters = [char for char in caller.db._playable_characters if char != delobj] delobj.delete() self.msg("Character '%s' was permanently deleted." % key) else: self.msg("Deletion was aborted.") del caller.ndb._char_to_delete match = match[0] player.ndb._char_to_delete = match prompt = "|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?" get_input(player, prompt % match.key, _callback)
def add(self, tag=None, category=None, data=None): "Add a new tag to the handler. Tag is a string or a list of strings." if not tag: return for tagstr in make_iter(tag): if not tagstr: continue tagstr = tagstr.strip().lower() category = category.strip().lower( ) if category is not None else None data = str(data) if data is not None else None # this will only create tag if no matches existed beforehand (it # will overload data on an existing tag since that is not # considered part of making the tag unique) tagobj = self.obj.__class__.objects.create_tag( key=tagstr, category=category, data=data, tagtype=self._tagtype) getattr(self.obj, self._m2m_fieldname).add(tagobj) if self._cache is None: self._recache() cachestring = "%s-%s" % (tagstr, category) self._cache[cachestring] = tagobj
def msg_connections(self, text=None, exclude=None, from_obj=None, **kwargs): """ Emit message to all objects inside rooms connecting to this room via exits, and rooms this room connects to via exits. Args: text (str or tuple): Message to send. If a tuple, this should be on the valid OOB outmessage form `(message, {kwargs})`, where kwargs are optional data passed to the `text` outputfunc. exclude (list, optional): A list of objects not to send to. from_obj (Object, optional): An object designated as the "sender" of the message. See `DefaultObject.msg()` for more info. Kwargs: Keyword arguments will be passed on to `obj.msg()` for all messaged objects. """ # we also accept an outcommand on the form (message, {kwargs}) is_outcmd = text and is_iter(text) message = text[0] if is_outcmd else text outkwargs = text[1] if is_outcmd and len(text) > 1 else {} # Collect exit and entrance locations with no repeats. room = [] rooms.append(self.objects.filter(db_destination=self)) rooms.append([exit.destination for exit in self.exits]) rooms = list(set(rooms)) if self in rooms: rooms.remove(self) if exclude: exclude = make_iter(exclude) rooms = [room for room in rooms if room not in exclude] for room in rooms: room.msg_contents(text=(message, outkwargs), from_obj=from_obj, **kwargs)
def tt_msg( self, message, from_obj, exclude=None, is_ooc=False, msg_type=TT_SAY, options=None, ): """ Send msg to characters at table. Note that if this method was simply named 'msg' rather than tt_msg, it would be called by msg_contents in rooms, causing characters at the places to receive redundant messages, since they are still objects in the room as well. """ # utils.make_iter checks to see if an object is a list, set, etc, and encloses it in a list if not # needed so that 'ob not in exclude' can function if we're just passed a character exclude = make_iter(exclude) for ob in self.item_data.occupants: if ob not in exclude: place_msg = self.build_tt_msg(from_obj, ob, message, is_ooc, msg_type) ob.msg(place_msg, from_obj=from_obj, options=options) from_obj.posecount += 1
def unpuppet_object(self, session): """ Disengage control over an object. Args: session (Session or list): The session or a list of sessions to disengage from their puppets. Raises: RuntimeError With message about error. """ for session in make_iter(session): obj = session.puppet if obj: # do the disconnect, but only if we are the last session to puppet obj.at_pre_unpuppet() obj.sessions.remove(session) if not obj.sessions.count(): del obj.account obj.at_post_unpuppet(self, session=session) # Just to be sure we're always clear. session.puppet = None session.puid = None
def remove(self, key=None, category=None): """ Remove a tag from the handler based ond key and/or category. Args: key (str or list, optional): The tag or tags to retrieve. category (str, optional): The Tag category to limit the request to. Note that `None` is the valid, default category Notes: If neither key nor category is specified, this acts as .clear(). """ if not key: # only category self.clear(category=category) return for key in make_iter(key): if not (key or key.strip()): # we don't allow empty tags continue tagstr = key.strip().lower() category = category.strip().lower() if category else category # This does not delete the tag object itself. Maybe it should do # that when no objects reference the tag anymore (but how to check)? # For now, tags are never deleted, only their connection to objects. tagobj = getattr(self.obj, self._m2m_fieldname).filter( db_key=tagstr, db_category=category, db_model=self._model, db_tagtype=self._tagtype) if tagobj: getattr(self.obj, self._m2m_fieldname).remove(tagobj[0]) self._delcache(key, category)
def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_edit=True): """ Collate a list of found prototypes based on search criteria and access. Args: caller (Account or Object): The object requesting the list. key (str, optional): Exact or partial prototype key to query for. tags (str or list, optional): Tag key or keys to query for. show_non_use (bool, optional): Show also prototypes the caller may not use. show_non_edit (bool, optional): Show also prototypes the caller may not edit. Returns: table (EvTable or None): An EvTable representation of the prototypes. None if no prototypes were found. """ # this allows us to pass lists of empty strings tags = [tag for tag in make_iter(tags) if tag] # get prototypes for readonly and db-based prototypes prototypes = search_prototype(key, tags) # get use-permissions of readonly attributes (edit is always False) display_tuples = [] for prototype in sorted(prototypes, key=lambda d: d.get('prototype_key', '')): lock_use = caller.locks.check_lockstring( caller, prototype.get('prototype_locks', ''), access_type='spawn', default=True) if not show_non_use and not lock_use: continue if prototype.get('prototype_key', '') in _MODULE_PROTOTYPES: lock_edit = False else: lock_edit = caller.locks.check_lockstring( caller, prototype.get('prototype_locks', ''), access_type='edit', default=True) if not show_non_edit and not lock_edit: continue ptags = [] for ptag in prototype.get('prototype_tags', []): if is_iter(ptag): if len(ptag) > 1: ptags.append("{} (category: {}".format(ptag[0], ptag[1])) else: ptags.append(ptag[0]) else: ptags.append(str(ptag)) display_tuples.append( (prototype.get('prototype_key', '<unset>'), prototype.get('prototype_desc', '<unset>'), "{}/{}".format('Y' if lock_use else 'N', 'Y' if lock_edit else 'N'), ",".join(ptags))) if not display_tuples: return "" table = [] width = 78 for i in range(len(display_tuples[0])): table.append([str(display_tuple[i]) for display_tuple in display_tuples]) table = EvTable("Key", "Desc", "Spawn/Edit", "Tags", table=table, crop=True, width=width) table.reformat_column(0, width=22) table.reformat_column(1, width=29) table.reformat_column(2, width=11, align='c') table.reformat_column(3, width=16) return table
def validate_prototype(prototype, protkey=None, protparents=None, is_prototype_base=True, strict=True, _flags=None): """ Run validation on a prototype, checking for inifinite regress. Args: prototype (dict): Prototype to validate. protkey (str, optional): The name of the prototype definition. If not given, the prototype dict needs to have the `prototype_key` field set. protpartents (dict, optional): The available prototype parent library. If note given this will be determined from settings/database. is_prototype_base (bool, optional): We are trying to create a new object *based on this object*. This means we can't allow 'mixin'-style prototypes without typeclass/parent etc. strict (bool, optional): If unset, don't require needed keys, only check against infinite recursion etc. _flags (dict, optional): Internal work dict that should not be set externally. Raises: RuntimeError: If prototype has invalid structure. RuntimeWarning: If prototype has issues that would make it unsuitable to build an object with (it may still be useful as a mix-in prototype). """ assert isinstance(prototype, dict) if _flags is None: _flags = {"visited": [], "depth": 0, "typeclass": False, "errors": [], "warnings": []} if not protparents: protparents = {prototype.get('prototype_key', "").lower(): prototype for prototype in search_prototype()} protkey = protkey and protkey.lower() or prototype.get('prototype_key', None) if strict and not bool(protkey): _flags['errors'].append("Prototype lacks a `prototype_key`.") protkey = "[UNSET]" typeclass = prototype.get('typeclass') prototype_parent = prototype.get('prototype_parent', []) if strict and not (typeclass or prototype_parent): if is_prototype_base: _flags['errors'].append("Prototype {} requires `typeclass` " "or 'prototype_parent'.".format(protkey)) else: _flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks " "a typeclass or a prototype_parent.".format(protkey)) if strict and typeclass: try: class_from_module(typeclass) except ImportError as err: _flags['errors'].append( "{}: Prototype {} is based on typeclass {}, which could not be imported!".format( err, protkey, typeclass)) # recursively traverese prototype_parent chain for protstring in make_iter(prototype_parent): protstring = protstring.lower() if protkey is not None and protstring == protkey: _flags['errors'].append("Prototype {} tries to parent itself.".format(protkey)) protparent = protparents.get(protstring) if not protparent: _flags['errors'].append("Prototype {}'s prototype_parent '{}' was not found.".format( (protkey, protstring))) if id(prototype) in _flags['visited']: _flags['errors'].append( "{} has infinite nesting of prototypes.".format(protkey or prototype)) if _flags['errors']: raise RuntimeError("Error: " + "\nError: ".join(_flags['errors'])) _flags['visited'].append(id(prototype)) _flags['depth'] += 1 validate_prototype(protparent, protstring, protparents, is_prototype_base=is_prototype_base, _flags=_flags) _flags['visited'].pop() _flags['depth'] -= 1 if typeclass and not _flags['typeclass']: _flags['typeclass'] = typeclass # if we get back to the current level without a typeclass it's an error. if strict and is_prototype_base and _flags['depth'] <= 0 and not _flags['typeclass']: _flags['errors'].append("Prototype {} has no `typeclass` defined anywhere in its parent\n " "chain. Add `typeclass`, or a `prototype_parent` pointing to a " "prototype with a typeclass.".format(protkey)) if _flags['depth'] <= 0: if _flags['errors']: raise RuntimeError("Error: " + "\nError: ".join(_flags['errors'])) if _flags['warnings']: raise RuntimeWarning("Warning: " + "\nWarning: ".join(_flags['warnings'])) # make sure prototype_locks are set to defaults prototype_locks = [lstring.split(":", 1) for lstring in prototype.get("prototype_locks", "").split(';') if ":" in lstring] locktypes = [tup[0].strip() for tup in prototype_locks] if "spawn" not in locktypes: prototype_locks.append(("spawn", "all()")) if "edit" not in locktypes: prototype_locks.append(("edit", "all()")) prototype_locks = ";".join(":".join(tup) for tup in prototype_locks) prototype['prototype_locks'] = prototype_locks
def save_prototype(**kwargs): """ Create/Store a prototype persistently. Kwargs: prototype_key (str): This is required for any storage. All other kwargs are considered part of the new prototype dict. Returns: prototype (dict or None): The prototype stored using the given kwargs, None if deleting. Raises: prototypes.ValidationError: If prototype does not validate. Note: No edit/spawn locks will be checked here - if this function is called the caller is expected to have valid permissions. """ kwargs = homogenize_prototype(kwargs) def _to_batchtuple(inp, *args): "build tuple suitable for batch-creation" if is_iter(inp): # already a tuple/list, use as-is return inp return (inp, ) + args prototype_key = kwargs.get("prototype_key") if not prototype_key: raise ValidationError("Prototype requires a prototype_key") prototype_key = str(prototype_key).lower() # we can't edit a prototype defined in a module if prototype_key in _MODULE_PROTOTYPES: mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A") raise PermissionError("{} is a read-only prototype " "(defined as code in {}).".format(prototype_key, mod)) # make sure meta properties are included with defaults stored_prototype = DbPrototype.objects.filter(db_key=prototype_key) prototype = stored_prototype[0].prototype if stored_prototype else {} kwargs['prototype_desc'] = kwargs.get("prototype_desc", prototype.get("prototype_desc", "")) prototype_locks = kwargs.get( "prototype_locks", prototype.get('prototype_locks', "spawn:all();edit:perm(Admin)")) is_valid, err = validate_lockstring(prototype_locks) if not is_valid: raise ValidationError("Lock error: {}".format(err)) kwargs['prototype_locks'] = prototype_locks prototype_tags = [ _to_batchtuple(tag, _PROTOTYPE_TAG_META_CATEGORY) for tag in make_iter(kwargs.get("prototype_tags", prototype.get('prototype_tags', [])))] kwargs["prototype_tags"] = prototype_tags prototype.update(kwargs) if stored_prototype: # edit existing prototype stored_prototype = stored_prototype[0] stored_prototype.desc = prototype['prototype_desc'] if prototype_tags: stored_prototype.tags.clear(category=_PROTOTYPE_TAG_CATEGORY) stored_prototype.tags.batch_add(*prototype['prototype_tags']) stored_prototype.locks.add(prototype['prototype_locks']) stored_prototype.attributes.add('prototype', prototype) else: # create a new prototype stored_prototype = create_script( DbPrototype, key=prototype_key, desc=prototype['prototype_desc'], persistent=True, locks=prototype_locks, tags=prototype['prototype_tags'], attributes=[("prototype", prototype)]) return stored_prototype.prototype
def search_prototype(key=None, tags=None): """ Find prototypes based on key and/or tags, or all prototypes. Kwargs: key (str): An exact or partial key to query for. tags (str or list): Tag key or keys to query for. These will always be applied with the 'db_protototype' tag category. Return: matches (list): All found prototype dicts. If no keys or tags are given, all available prototypes will be returned. Note: The available prototypes is a combination of those supplied in PROTOTYPE_MODULES and those stored in the database. Note that if tags are given and the prototype has no tags defined, it will not be found as a match. """ # search module prototypes mod_matches = {} if tags: # use tags to limit selection tagset = set(tags) mod_matches = {prototype_key: prototype for prototype_key, prototype in _MODULE_PROTOTYPES.items() if tagset.intersection(prototype.get("prototype_tags", []))} else: mod_matches = _MODULE_PROTOTYPES if key: if key in mod_matches: # exact match module_prototypes = [mod_matches[key]] else: # fuzzy matching module_prototypes = [prototype for prototype_key, prototype in mod_matches.items() if key in prototype_key] else: module_prototypes = [match for match in mod_matches.values()] # search db-stored prototypes if tags: # exact match on tag(s) tags = make_iter(tags) tag_categories = ["db_prototype" for _ in tags] db_matches = DbPrototype.objects.get_by_tag(tags, tag_categories) else: db_matches = DbPrototype.objects.all() if key: # exact or partial match on key db_matches = db_matches.filter(db_key=key) or db_matches.filter(db_key__icontains=key) # return prototype db_prototypes = [dbprot.prototype for dbprot in db_matches] matches = db_prototypes + module_prototypes nmatches = len(matches) if nmatches > 1 and key: key = key.lower() # avoid duplicates if an exact match exist between the two types filter_matches = [mta for mta in matches if mta.get('prototype_key') and mta['prototype_key'] == key] if filter_matches and len(filter_matches) < nmatches: matches = filter_matches return matches
def search_object(self, searchdata, attribute_name=None, typeclass=None, candidates=None, exact=True, use_dbref=True): """ Search as an object globally or in a list of candidates and return results. The result is always an Object. Always returns a list. Args: searchdata (str or Object): The entity to match for. This is usually a key string but may also be an object itself. By default (if no `attribute_name` is set), this will search `object.key` and `object.aliases` in order. Can also be on the form #dbref, which will (if `exact=True`) be matched against primary key. attribute_name (str): Use this named Attribute to match searchdata against, instead of the defaults. If this is the name of a database field (with or without the `db_` prefix), that will be matched too. typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help speed up global searches. candidates (list): If supplied, search will only be performed among the candidates in this list. A common list of candidates is the contents of the current location searched. exact (bool): Match names/aliases exactly or partially. Partial matching matches the beginning of words in the names/aliases, using a matching routine to separate multiple matches in names with multiple components (so "bi sw" will match "Big sword"). Since this is more expensive than exact matching, it is recommended to be used together with the `candidates` keyword to limit the number of possibilities. This value has no meaning if searching for attributes/properties. use_dbref (bool): If False, bypass direct lookup of a string on the form #dbref and treat it like any string. Returns: matches (list): Matching objects """ def _searcher(searchdata, candidates, typeclass, exact=False): """ Helper method for searching objects. `typeclass` is only used for global searching (no candidates) """ if attribute_name: # attribute/property search (always exact). matches = self.get_objs_with_db_property_value( attribute_name, searchdata, candidates=candidates, typeclasses=typeclass) if matches: return matches return self.get_objs_with_attr_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass) else: # normal key/alias search return self.get_objs_with_key_or_alias(searchdata, exact=exact, candidates=candidates, typeclasses=typeclass) if not searchdata and searchdata != 0: return [] if typeclass: # typeclass may also be a list typeclasses = make_iter(typeclass) for i, typeclass in enumerate(make_iter(typeclasses)): if callable(typeclass): typeclasses[i] = u"%s.%s" % (typeclass.__module__, typeclass.__name__) else: typeclasses[i] = u"%s" % typeclass typeclass = typeclasses if candidates is not None: if not candidates: # candidates is the empty list. This should mean no matches can ever be acquired. return [] # Convenience check to make sure candidates are really dbobjs candidates = [cand for cand in make_iter(candidates) if cand] if typeclass: candidates = [ cand for cand in candidates if _GA(cand, "db_typeclass_path") in typeclass ] dbref = not attribute_name and exact and use_dbref and self.dbref( searchdata) if dbref: # 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] else: return [] # Search through all possibilities. match_number = None # always run first check exact - we don't want partial matches # if on the form of 1-keyword etc. matches = _searcher(searchdata, candidates, typeclass, exact=True) if not matches: # no matches found - check if we are dealing with N-keyword # query - if so, strip it. match = _MULTIMATCH_REGEX.match(searchdata) match_number = None if match: # strips the number match_number, searchdata = match.group("number"), match.group( "name") match_number = int(match_number) - 1 match_number = match_number if match_number >= 0 else None if match_number is not None or not exact: # run search again, with the exactness set by call matches = _searcher(searchdata, candidates, typeclass, exact=exact) # deal with result if len(matches) > 1 and match_number is not None: # multiple matches, but a number was given to separate them try: matches = [matches[match_number]] except IndexError: # match number not matching anything pass # return a list (possibly empty) return matches
def search_prototype(key=None, tags=None, require_single=False, return_iterators=False): """ Find prototypes based on key and/or tags, or all prototypes. Kwargs: key (str): An exact or partial key to query for. tags (str or list): Tag key or keys to query for. These will always be applied with the 'db_protototype' tag category. require_single (bool): If set, raise KeyError if the result was not found or if there are multiple matches. return_iterators (bool): Optimized return for large numbers of db-prototypes. If set, separate returns of module based prototypes and paginate the db-prototype return. Return: matches (list): Default return, all found prototype dicts. Empty list if no match was found. Note that if neither `key` nor `tags` were given, *all* available prototypes will be returned. list, queryset: If `return_iterators` are found, this is a list of module-based prototypes followed by a *paginated* queryset of db-prototypes. Raises: KeyError: If `require_single` is True and there are 0 or >1 matches. Note: The available prototypes is a combination of those supplied in PROTOTYPE_MODULES and those stored in the database. Note that if tags are given and the prototype has no tags defined, it will not be found as a match. """ # search module prototypes mod_matches = {} if tags: # use tags to limit selection tagset = set(tags) mod_matches = { prototype_key: prototype for prototype_key, prototype in _MODULE_PROTOTYPES.items() if tagset.intersection(prototype.get("prototype_tags", [])) } else: mod_matches = _MODULE_PROTOTYPES if key: if key in mod_matches: # exact match module_prototypes = [mod_matches[key]] else: # fuzzy matching module_prototypes = [ prototype for prototype_key, prototype in mod_matches.items() if key in prototype_key ] else: module_prototypes = [match for match in mod_matches.values()] # search db-stored prototypes if tags: # exact match on tag(s) tags = make_iter(tags) tag_categories = ["db_prototype" for _ in tags] db_matches = DbPrototype.objects.get_by_tag(tags, tag_categories) else: db_matches = DbPrototype.objects.all().order_by("id") if key: # exact or partial match on key db_matches = (db_matches.filter( Q(db_key__iexact=key) | Q(db_key__icontains=key)).order_by("id")) # convert to prototype db_ids = db_matches.values_list("id", flat=True) db_matches = (Attribute.objects.filter(scriptdb__pk__in=db_ids, db_key="prototype").values_list( "db_value", flat=True)) if key: matches = list(db_matches) + module_prototypes nmatches = len(matches) if nmatches > 1: key = key.lower() # avoid duplicates if an exact match exist between the two types filter_matches = [ mta for mta in matches if mta.get("prototype_key") and mta["prototype_key"] == key ] if filter_matches and len(filter_matches) < nmatches: matches = filter_matches nmatches = len(matches) if nmatches != 1 and require_single: raise KeyError("Found {} matching prototypes.".format(nmatches)) return matches elif return_iterators: # trying to get the entire set of prototypes - we must paginate # we must paginate the result of trying to fetch the entire set db_pages = Paginator(db_matches, 500) return module_prototypes, db_pages else: # full fetch, no pagination return list(db_matches) + module_prototypes
def create_script( typeclass=None, key=None, obj=None, account=None, locks=None, interval=None, start_delay=None, repeats=None, persistent=None, autostart=True, report_to=None, desc=None, tags=None, attributes=None, ): """ Create a new script. All scripts are a combination of a database object that communicates with the database, and an typeclass that 'decorates' the database object into being different types of scripts. It's behaviour is similar to the game objects except scripts has a time component and are more limited in scope. Kwargs: typeclass (class or str): Class or python path to a typeclass. key (str): Name of the new object. If not set, a name of #dbref will be set. obj (Object): The entity on which this Script sits. If this is `None`, we are creating a "global" script. account (Account): The account on which this Script sits. It is exclusiv to `obj`. locks (str): one or more lockstrings, separated by semicolons. interval (int): The triggering interval for this Script, in seconds. If unset, the Script will not have a timing component. start_delay (bool): If `True`, will wait `interval` seconds before triggering the first time. repeats (int): The number of times to trigger before stopping. If unset, will repeat indefinitely. persistent (bool): If this Script survives a server shutdown or not (all Scripts will survive a reload). autostart (bool): If this Script will start immediately when created or if the `start` method must be called explicitly. report_to (Object): The object to return error messages to. desc (str): Optional description of script tags (list): List of tags or tuples (tag, category). attributes (list): List if tuples (key, value) or (key, value, category) (key, value, lockstring) or (key, value, lockstring, default_access). See evennia.scripts.manager for methods to manipulate existing scripts in the database. """ global _ScriptDB if not _ScriptDB: from evennia.scripts.models import ScriptDB as _ScriptDB typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS if isinstance(typeclass, str): # a path is given. Load the actual typeclass typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) # validate input kwarg = {} if key: kwarg["db_key"] = key if account: kwarg["db_account"] = dbid_to_obj(account, _AccountDB) if obj: kwarg["db_obj"] = dbid_to_obj(obj, _ObjectDB) if interval: kwarg["db_interval"] = max(0, interval) if start_delay: kwarg["db_start_delay"] = start_delay if repeats: kwarg["db_repeats"] = max(0, repeats) if persistent: kwarg["db_persistent"] = persistent if desc: kwarg["db_desc"] = desc tags = make_iter(tags) if tags is not None else None attributes = make_iter(attributes) if attributes is not None else None # create new instance new_script = typeclass(**kwarg) # store the call signature for the signal new_script._createdict = dict( key=key, obj=obj, account=account, locks=locks, interval=interval, start_delay=start_delay, repeats=repeats, persistent=persistent, autostart=autostart, report_to=report_to, desc=desc, tags=tags, attributes=attributes, ) # this will trigger the save signal which in turn calls the # at_first_save hook on the typeclass, where the _createdict # can be used. new_script.save() if not new_script.id: # this happens in the case of having a repeating script with `repeats=1` and # `start_delay=False` - the script will run once and immediately stop before save is over. return None signals.SIGNAL_SCRIPT_POST_CREATE.send(sender=new_script) return new_script
def func(self): """Create the nickname""" def _cy(string): "add color to the special markers" return re.sub(r"(\$[0-9]+|\*|\?|\[.+?\])", r"|Y\1|n", string) caller = self.caller switches = self.switches nicktypes = [ switch for switch in switches if switch in ("object", "account", "inputline") ] specified_nicktype = bool(nicktypes) nicktypes = nicktypes if specified_nicktype else ["inputline"] nicklist = ( utils.make_iter( caller.nicks.get(category="inputline", return_obj=True) or []) + utils.make_iter( caller.nicks.get(category="object", return_obj=True) or []) + utils.make_iter( caller.nicks.get(category="account", return_obj=True) or [])) if "list" in switches or self.cmdstring in ("nicks", ): if not nicklist: string = "|wNo nicks defined.|n" else: table = self.styled_table("#", "Type", "Nick match", "Replacement") for inum, nickobj in enumerate(nicklist): _, _, nickvalue, replacement = nickobj.value table.add_row(str(inum + 1), nickobj.db_category, _cy(nickvalue), _cy(replacement)) string = "|wDefined Nicks:|n\n%s" % table caller.msg(string) return if "clearall" in switches: caller.nicks.clear() caller.account.nicks.clear() caller.msg("Cleared all nicks.") return if "delete" in switches or "del" in switches: if not self.args or not self.lhs: caller.msg( "usage nick/delete <nick> or <#num> ('nicks' for list)") return # see if a number was given arg = self.args.lstrip("#") oldnicks = [] if arg.isdigit(): # we are given a index in nicklist delindex = int(arg) if 0 < delindex <= len(nicklist): oldnicks.append(nicklist[delindex - 1]) else: caller.msg( "Not a valid nick index. See 'nicks' for a list.") return else: if not specified_nicktype: nicktypes = ("object", "account", "inputline") for nicktype in nicktypes: oldnicks.append( caller.nicks.get(arg, category=nicktype, return_obj=True)) oldnicks = [oldnick for oldnick in oldnicks if oldnick] if oldnicks: for oldnick in oldnicks: nicktype = oldnick.category nicktypestr = "%s-nick" % nicktype.capitalize() _, _, old_nickstring, old_replstring = oldnick.value caller.nicks.remove(old_nickstring, category=nicktype) caller.msg("%s removed: '|w%s|n' -> |w%s|n." % (nicktypestr, old_nickstring, old_replstring)) else: caller.msg("No matching nicks to remove.") return if not self.rhs and self.lhs: # check what a nick is set to strings = [] if not specified_nicktype: nicktypes = ("object", "account", "inputline") for nicktype in nicktypes: nicks = [ nick for nick in utils.make_iter( caller.nicks.get(category=nicktype, return_obj=True)) if nick ] for nick in nicks: _, _, nick, repl = nick.value if nick.startswith(self.lhs): strings.append("{}-nick: '{}' -> '{}'".format( nicktype.capitalize(), nick, repl)) if strings: caller.msg("\n".join(strings)) else: caller.msg("No nicks found matching '{}'".format(self.lhs)) return if not self.rhs and self.lhs: # check what a nick is set to strings = [] if not specified_nicktype: nicktypes = ("object", "account", "inputline") for nicktype in nicktypes: if nicktype == "account": obj = account else: obj = caller nicks = utils.make_iter( obj.nicks.get(category=nicktype, return_obj=True)) for nick in nicks: _, _, nick, repl = nick.value if nick.startswith(self.lhs): strings.append("{}-nick: '{}' -> '{}'".format( nicktype.capitalize(), nick, repl)) if strings: caller.msg("\n".join(strings)) else: caller.msg("No nicks found matching '{}'".format(self.lhs)) return if not self.rhs and self.lhs: # check what a nick is set to strings = [] if not specified_nicktype: nicktypes = ("object", "account", "inputline") for nicktype in nicktypes: if nicktype == "account": obj = account else: obj = caller nicks = utils.make_iter( obj.nicks.get(category=nicktype, return_obj=True)) for nick in nicks: _, _, nick, repl = nick.value if nick.startswith(self.lhs): strings.append("{}-nick: '{}' -> '{}'".format( nicktype.capitalize(), nick, repl)) if strings: caller.msg("\n".join(strings)) else: caller.msg("No nicks found matching '{}'".format(self.lhs)) return if not self.args or not self.lhs: caller.msg("Usage: nick[/switches] nickname = [realname]") return # setting new nicks nickstring = self.lhs replstring = self.rhs if replstring == nickstring: caller.msg( "No point in setting nick same as the string to replace...") return # check so we have a suitable nick type errstring = "" string = "" for nicktype in nicktypes: nicktypestr = "%s-nick" % nicktype.capitalize() old_nickstring = None old_replstring = None oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True) if oldnick: _, _, old_nickstring, old_replstring = oldnick.value if replstring: # creating new nick errstring = "" if oldnick: if replstring == old_replstring: string += "\nIdentical %s already set." % nicktypestr.lower( ) else: string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % ( nicktypestr, old_nickstring, replstring, ) else: string += "\n%s '|w%s|n' mapped to '|w%s|n'." % ( nicktypestr, nickstring, replstring, ) try: caller.nicks.add(nickstring, replstring, category=nicktype) except NickTemplateInvalid: caller.msg( "You must use the same $-markers both in the nick and in the replacement." ) return elif old_nickstring and old_replstring: # just looking at the nick string += "\n%s '|w%s|n' maps to '|w%s|n'." % ( nicktypestr, old_nickstring, old_replstring, ) errstring = "" string = errstring if errstring else string caller.msg(_cy(string))
from evennia.server.sessionhandler import SESSIONS _SA = object.__setattr__ if os.name == 'nt': # For Windows we need to handle pid files manually. SERVER_PIDFILE = os.path.join(settings.GAME_DIR, "server", 'server.pid') # a file with a flag telling the server to restart after shutdown or not. SERVER_RESTART = os.path.join(settings.GAME_DIR, "server", 'server.restart') # module containing hook methods called during start_stop SERVER_STARTSTOP_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE) # modules containing plugin services SERVER_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.SERVER_SERVICES_PLUGIN_MODULES)] try: WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE) except ImportError: WEB_PLUGINS_MODULE = None print ("WARNING: settings.WEB_PLUGINS_MODULE not found - " "copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf.") #------------------------------------------------------------ # Evennia Server settings #------------------------------------------------------------ SERVERNAME = settings.SERVERNAME VERSION = get_evennia_version() AMP_ENABLED = True
def spawn(*prototypes, **kwargs): """ Spawn a number of prototyped objects. Each argument should be a prototype dictionary. keyword args: prototype_modules - a python-path to a prototype module, or a list of such paths. These will be used to build the global protparents dictionary accessible by the input prototypes. If not given, it will instead look for modules defined by settings.PROTOTYPE_MODULES. prototype_parents - a dictionary holding a custom prototype-parent dictionary. Will overload same-named prototypes from prototype_modules. return_prototypes - only return a list of the prototype-parents (no object creation happens) """ protparents = {} protmodules = make_iter(kwargs.get("prototype_modules", [])) if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"): protmodules = make_iter(settings.PROTOTYPE_MODULES) for prototype_module in protmodules: protparents.update( dict((key, val) for key, val in all_from_module(prototype_module).items() if isinstance(val, dict))) #overload module's protparents with specifically given protparents protparents.update(kwargs.get("prototype_parents", {})) for key, prototype in protparents.items(): _validate_prototype(key, prototype, protparents, []) if "return_prototypes" in kwargs: # only return the parents return copy.deepcopy(protparents) objsparams = [] for prototype in prototypes: _validate_prototype(None, prototype, protparents, []) prot = _get_prototype(prototype, {}, protparents) if not prot: continue # extract the keyword args we need to create the object itself create_kwargs = {} create_kwargs["db_key"] = prot.pop( "key", "Spawned Object %06i" % randint(1, 100000)) create_kwargs["db_location"] = _handle_dbref(prot.pop( "location", None)) create_kwargs["db_home"] = _handle_dbref( prot.pop("home", settings.DEFAULT_HOME)) create_kwargs["db_destination"] = _handle_dbref( prot.pop("destination", None)) create_kwargs["db_typeclass_path"] = prot.pop( "typeclass", settings.BASE_OBJECT_TYPECLASS) # extract calls to handlers permission_string = prot.pop("permissions", "") lock_string = prot.pop("locks", "") alias_string = prot.pop("aliases", "") tags = prot.pop("tags", "") # extract ndb assignments nattributes = dict( (key.split("_", 1)[1], value if callable(value) else value) for key, value in prot.items() if key.startswith("ndb_")) # the rest are attributes attributes = dict( (key, value() if callable(value) else value) for key, value in prot.items() if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_"))) # pack for call into _batch_create_object objsparams.append((create_kwargs, permission_string, lock_string, alias_string, nattributes, attributes, tags)) return _batch_create_object(*objsparams)
def func(self): """Create the nickname""" caller = self.caller switches = self.switches nicktypes = [ switch for switch in switches if switch in ("object", "account", "inputline") ] or ["inputline"] nicklist = utils.make_iter(caller.nicks.get(return_obj=True) or []) if 'list' in switches or self.cmdstring in ("nicks", "@nicks"): if not nicklist: string = "|wNo nicks defined.|n" else: table = evtable.EvTable("#", "Type", "Nick match", "Replacement") for inum, nickobj in enumerate(nicklist): _, _, nickvalue, replacement = nickobj.value table.add_row(str(inum + 1), nickobj.db_category, nickvalue, replacement) string = "|wDefined Nicks:|n\n%s" % table caller.msg(string) return if 'clearall' in switches: caller.nicks.clear() caller.msg("Cleared all nicks.") return if not self.args or not self.lhs: caller.msg("Usage: nick[/switches] nickname = [realname]") return nickstring = self.lhs replstring = self.rhs old_nickstring = None old_replstring = None if replstring == nickstring: caller.msg( "No point in setting nick same as the string to replace...") return # check so we have a suitable nick type errstring = "" string = "" for nicktype in nicktypes: oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True) if oldnick: _, _, old_nickstring, old_replstring = oldnick.value else: # no old nick, see if a number was given arg = self.args.lstrip("#") if arg.isdigit(): # we are given a index in nicklist delindex = int(arg) if 0 < delindex <= len(nicklist): oldnick = nicklist[delindex - 1] _, _, old_nickstring, old_replstring = oldnick.value else: errstring += "Not a valid nick index." else: errstring += "Nick not found." if "delete" in switches or "del" in switches: # clear the nick if old_nickstring and caller.nicks.has(old_nickstring, category=nicktype): caller.nicks.remove(old_nickstring, category=nicktype) string += "\nNick removed: '|w%s|n' -> |w%s|n." % ( old_nickstring, old_replstring) else: errstring += "\nNick '|w%s|n' was not deleted." % old_nickstring elif replstring: # creating new nick errstring = "" if oldnick: string += "\nNick '|w%s|n' updated to map to '|w%s|n'." % ( old_nickstring, replstring) else: string += "\nNick '|w%s|n' mapped to '|w%s|n'." % ( nickstring, replstring) try: caller.nicks.add(nickstring, replstring, category=nicktype) except NickTemplateInvalid: caller.msg( "You must use the same $-markers both in the nick and in the replacement." ) return elif old_nickstring and old_replstring: # just looking at the nick string += "\nNick '|w%s|n' maps to '|w%s|n'." % ( old_nickstring, old_replstring) errstring = "" string = errstring if errstring else string caller.msg(string)
def get(self, key=None, default=None, category=None, return_obj=False, strattr=False, raise_exception=False, accessing_obj=None, default_access=True, return_list=False): """ Get the Attribute. Args: key (str or list, optional): the attribute identifier or multiple attributes to get. if a list of keys, the method will return a list. category (str, optional): the category within which to retrieve attribute(s). default (any, optional): The value to return if an Attribute was not defined. If set, it will be returned in a one-item list. return_obj (bool, optional): If set, the return is not the value of the Attribute but the Attribute object itself. strattr (bool, optional): Return the `strvalue` field of the Attribute rather than the usual `value`, this is a string-only value for quick database searches. raise_exception (bool, optional): When an Attribute is not found, the return from this is usually `default`. If this is set, an exception is raised instead. accessing_obj (object, optional): If set, an `attrread` permission lock will be checked before returning each looked-after Attribute. default_access (bool, optional): If no `attrread` lock is set on object, this determines if the lock should then be passed or not. return_list (bool, optional): Returns: result (any or list): One or more matches for keys and/or categories. Each match will be the value of the found Attribute(s) unless `return_obj` is True, at which point it will be the attribute object itself or None. If `return_list` is True, this will always be a list, regardless of the number of elements. Raises: AttributeError: If `raise_exception` is set and no matching Attribute was found matching `key`. """ class RetDefault(object): """Holds default values""" def __init__(self): self.key = None self.value = default self.category = None self.strvalue = str(default) if default is not None else None ret = [] for keystr in make_iter(key): # it's okay to send a None key attr_objs = self._getcache(keystr, category) if attr_objs: ret.extend(attr_objs) elif raise_exception: raise AttributeError elif return_obj: ret.append(None) else: ret.append(RetDefault()) if accessing_obj: # check 'attrread' locks ret = [ attr for attr in ret if attr.access( accessing_obj, self._attrread, default=default_access) ] if strattr: ret = ret if return_obj else [ attr.strvalue for attr in ret if attr ] else: ret = ret if return_obj else [attr.value for attr in ret if attr] if return_list: return ret if ret else [default] if default is not None else [] elif not ret: return ret if len(key) > 1 else default return ret[0] if len(ret) == 1 else ret
def homogenize_prototype(prototype, custom_keys=None): """ Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form. Args: prototype (dict): Prototype. custom_keys (list, optional): Custom keys which should not be interpreted as attrs, beyond the default reserved keys. Returns: homogenized (dict): Prototype where all non-identified keys grouped as attributes and other homogenizations like adding missing prototype_keys and setting a default typeclass. """ if not prototype or not isinstance(prototype, dict): return {} reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ()) # correct cases of setting None for certain values for protkey in prototype: if prototype[protkey] is None: if protkey in ("attrs", "tags", "prototype_tags"): prototype[protkey] = [] elif protkey in ("prototype_key", "prototype_desc"): prototype[protkey] = "" attrs = list(prototype.get("attrs", [])) # break reference tags = make_iter(prototype.get("tags", [])) homogenized_tags = [] homogenized = {} for key, val in prototype.items(): if key in reserved: if key == "tags": for tag in tags: if not is_iter(tag): homogenized_tags.append((tag, None, None)) else: homogenized_tags.append(tag) else: homogenized[key] = val else: # unassigned keys -> attrs attrs.append((key, val, None, "")) if attrs: homogenized["attrs"] = attrs if homogenized_tags: homogenized["tags"] = homogenized_tags # add required missing parts that had defaults before homogenized["prototype_key"] = homogenized.get( "prototype_key", # assign a random hash as key "prototype-{}".format( hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:7]), ) homogenized["prototype_tags"] = homogenized.get("prototype_tags", []) homogenized["prototype_locks"] = homogenized.get("prototype_lock", _PROTOTYPE_FALLBACK_LOCK) homogenized["prototype_desc"] = homogenized.get("prototype_desc", "") if "typeclass" not in prototype and "prototype_parent" not in prototype: homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS return homogenized
# assign module path to each prototype_key for easy reference _MODULE_PROTOTYPE_MODULES.update( {prototype_key.lower(): mod for prototype_key, _ in prots}) # make sure the prototype contains all meta info for prototype_key, prot in prots: actual_prot_key = prot.get("prototype_key", prototype_key).lower() prot.update({ "prototype_key": actual_prot_key, "prototype_desc": prot["prototype_desc"] if "prototype_desc" in prot else mod, "prototype_locks": (prot["prototype_locks"] if "prototype_locks" in prot else "use:all();edit:false()"), "prototype_tags": list(set(make_iter(prot.get("prototype_tags", [])) + ["module"])), }) _MODULE_PROTOTYPES[actual_prot_key] = prot # Db-based prototypes class DbPrototype(DefaultScript): """ This stores a single prototype, in an Attribute `prototype`. """ def at_script_creation(self): self.key = "empty prototype" # prototype_key self.desc = "A prototype" # prototype_desc (.tags are used for prototype_tags) self.db.prototype = {} # actual prototype
def create_account( key, email, password, typeclass=None, is_superuser=False, locks=None, permissions=None, tags=None, attributes=None, report_to=None, ): """ This creates a new account. Args: key (str): The account's name. This should be unique. email (str or None): Email on valid [email protected] form. If the empty string, will be set to None. password (str): Password in cleartext. Kwargs: typeclass (str): The typeclass to use for the account. is_superuser (bool): Wether or not this account is to be a superuser locks (str): Lockstring. permission (list): List of permission strings. tags (list): List of Tags on form `(key, category[, data])` attributes (list): List of Attributes on form `(key, value [, category, [,lockstring [, default_pass]]])` report_to (Object): An object with a msg() method to report errors to. If not given, errors will be logged. Returns: Account: The newly created Account. Raises: ValueError: If `key` already exists in database. Notes: Usually only the server admin should need to be superuser, all other access levels can be handled with more fine-grained permissions or groups. A superuser bypasses all lock checking operations and is thus not suitable for play-testing the game. """ global _AccountDB if not _AccountDB: from evennia.accounts.models import AccountDB as _AccountDB typeclass = typeclass if typeclass else settings.BASE_ACCOUNT_TYPECLASS locks = make_iter(locks) if locks is not None else None permissions = make_iter(permissions) if permissions is not None else None tags = make_iter(tags) if tags is not None else None attributes = make_iter(attributes) if attributes is not None else None if isinstance(typeclass, str): # a path is given. Load the actual typeclass. typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) # setup input for the create command. We use AccountDB as baseclass # here to give us maximum freedom (the typeclasses will load # correctly when each object is recovered). if not email: email = None if _AccountDB.objects.filter(username__iexact=key): raise ValueError("An Account with the name '%s' already exists." % key) # this handles a given dbref-relocate to an account. report_to = dbid_to_obj(report_to, _AccountDB) # create the correct account entity, using the setup from # base django auth. now = timezone.now() email = typeclass.objects.normalize_email(email) new_account = typeclass( username=key, email=email, is_staff=is_superuser, is_superuser=is_superuser, last_login=now, date_joined=now, ) if password is not None: # the password may be None for 'fake' accounts, like bots valid, error = new_account.validate_password(password, new_account) if not valid: raise error new_account.set_password(password) new_account._createdict = dict(locks=locks, permissions=permissions, report_to=report_to, tags=tags, attributes=attributes) # saving will trigger the signal that calls the # at_first_save hook on the typeclass, where the _createdict # can be used. new_account.save() # note that we don't send a signal here, that is sent from the Account.create helper method # instead. return new_account
def get_by_tag(self, key=None, category=None, tagtype=None, **kwargs): """ Return objects having tags with a given key or category or combination of the two. Also accepts multiple tags/category/tagtype Args: key (str or list, optional): Tag key or list of keys. Not case sensitive. category (str or list, optional): Tag category. Not case sensitive. If `key` is a list, a single category can either apply to all keys in that list or this must be a list matching the `key` list element by element. If no `key` is given, all objects with tags of this category are returned. tagtype (str, optional): 'type' of Tag, by default this is either `None` (a normal Tag), `alias` or `permission`. This always apply to all queried tags. Kwargs: match (str): "all" (default) or "any"; determines whether the target object must be tagged with ALL of the provided tags/categories or ANY single one. ANY will perform a weighted sort, so objects with more tag matches will outrank those with fewer tag matches. Returns: objects (list): Objects with matching tag. Raises: IndexError: If `key` and `category` are both lists and `category` is shorter than `key`. """ if not (key or category): return [] global _Tag if not _Tag: from evennia.typeclasses.models import Tag as _Tag anymatch = "any" == kwargs.get("match", "all").lower().strip() keys = make_iter(key) if key else [] categories = make_iter(category) if category else [] n_keys = len(keys) n_categories = len(categories) unique_categories = sorted(set(categories)) n_unique_categories = len(unique_categories) dbmodel = self.model.__dbclass__.__name__.lower() query = (self.filter( db_tags__db_tagtype__iexact=tagtype, db_tags__db_model__iexact=dbmodel).distinct().order_by("id")) if n_keys > 0: # keys and/or categories given if n_categories == 0: categories = [None for _ in range(n_keys)] elif n_categories == 1 and n_keys > 1: cat = categories[0] categories = [cat for _ in range(n_keys)] elif 1 < n_categories < n_keys: raise IndexError( "get_by_tag needs a single category or a list of categories " "the same length as the list of tags.") clauses = Q() for ikey, key in enumerate(keys): # ANY mode; must match any one of the given tags/categories clauses |= Q(db_key__iexact=key, db_category__iexact=categories[ikey]) else: # only one or more categories given clauses = Q() # ANY mode; must match any one of them for category in unique_categories: clauses |= Q(db_category__iexact=category) tags = _Tag.objects.filter(clauses) query = query.filter(db_tags__in=tags).annotate(matches=Count( "db_tags__pk", filter=Q(db_tags__in=tags), distinct=True)) if anymatch: # ANY: Match any single tag, ordered by weight query = query.order_by("-matches") else: # Default ALL: Match all of the tags and optionally more n_req_tags = n_keys if n_keys > 0 else n_unique_categories query = query.filter(matches__gte=n_req_tags) return query
def create_object( typeclass=None, key=None, location=None, home=None, permissions=None, locks=None, aliases=None, tags=None, destination=None, report_to=None, nohome=False, attributes=None, nattributes=None, ): """ Create a new in-game object. Kwargs: typeclass (class or str): Class or python path to a typeclass. key (str): Name of the new object. If not set, a name of #dbref will be set. home (Object or str): Obj or #dbref to use as the object's home location. permissions (list): A list of permission strings or tuples (permstring, category). locks (str): one or more lockstrings, separated by semicolons. aliases (list): A list of alternative keys or tuples (aliasstring, category). tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data). destination (Object or str): Obj or #dbref to use as an Exit's target. report_to (Object): The object to return error messages to. nohome (bool): This allows the creation of objects without a default home location; only used when creating the default location itself or during unittests. attributes (list): Tuples on the form (key, value) or (key, value, category), (key, value, lockstring) or (key, value, lockstring, default_access). to set as Attributes on the new object. nattributes (list): Non-persistent tuples on the form (key, value). Note that adding this rarely makes sense since this data will not survive a reload. Returns: object (Object): A newly created object of the given typeclass. Raises: ObjectDB.DoesNotExist: If trying to create an Object with `location` or `home` that can't be found. """ global _ObjectDB if not _ObjectDB: from evennia.objects.models import ObjectDB as _ObjectDB typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS # convenience converters to avoid common usage mistake permissions = make_iter(permissions) if permissions is not None else None locks = make_iter(locks) if locks is not None else None aliases = make_iter(aliases) if aliases is not None else None tags = make_iter(tags) if tags is not None else None attributes = make_iter(attributes) if attributes is not None else None if isinstance(typeclass, str): # a path is given. Load the actual typeclass typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) # Setup input for the create command. We use ObjectDB as baseclass here # to give us maximum freedom (the typeclasses will load # correctly when each object is recovered). location = dbid_to_obj(location, _ObjectDB) destination = dbid_to_obj(destination, _ObjectDB) home = dbid_to_obj(home, _ObjectDB) if not home: try: home = dbid_to_obj(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None except _ObjectDB.DoesNotExist: raise _ObjectDB.DoesNotExist( "settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." % settings.DEFAULT_HOME) # create new instance new_object = typeclass( db_key=key, db_location=location, db_destination=destination, db_home=home, db_typeclass_path=typeclass.path, ) # store the call signature for the signal new_object._createdict = dict( key=key, location=location, destination=destination, home=home, typeclass=typeclass.path, permissions=permissions, locks=locks, aliases=aliases, tags=tags, report_to=report_to, nohome=nohome, attributes=attributes, nattributes=nattributes, ) # this will trigger the save signal which in turn calls the # at_first_save hook on the typeclass, where the _createdict can be # used. new_object.save() signals.SIGNAL_OBJECT_POST_CREATE.send(sender=new_object) return new_object
def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None): """ Args: ostring (str): A search criterion. exact (bool, optional): Require exact match of ostring (still case-insensitive). If `False`, will do fuzzy matching using `evennia.utils.utils.string_partial_matching` algorithm. candidates (list): Only match among these candidates. typeclasses (list): Only match objects with typeclasses having thess path strings. Returns: matches (list): A list of matches of length 0, 1 or more. """ if not isinstance(ostring, basestring): if hasattr(ostring, "key"): ostring = ostring.key else: return [] if is_iter(candidates) and not len(candidates): # if candidates is an empty iterable there can be no matches # Exit early. return [] # build query objects candidates_id = [ _GA(obj, "id") for obj in make_iter(candidates) if obj ] cand_restriction = candidates is not None and Q( pk__in=candidates_id) or Q() type_restriction = typeclasses and Q( db_typeclass_path__in=make_iter(typeclasses)) or Q() if exact: # exact match - do direct search return self.filter(cand_restriction & type_restriction & ( Q(db_key__iexact=ostring) | Q(db_tags__db_key__iexact=ostring) & Q(db_tags__db_tagtype__iexact="alias"))).distinct() elif candidates: # fuzzy with candidates search_candidates = self.filter(cand_restriction & type_restriction) else: # fuzzy without supplied candidates - we select our own candidates search_candidates = self.filter(type_restriction & ( Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct() # fuzzy matching key_strings = search_candidates.values_list("db_key", flat=True).order_by("id") 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 ] else: # match by alias rather than by key search_candidates = search_candidates.filter( db_tags__db_tagtype__iexact="alias", db_tags__db_key__icontains=ostring) alias_strings = [] alias_candidates = [] # TODO create the alias_strings and alias_candidates lists more efficiently? for candidate in search_candidates: for alias in candidate.aliases.all(): alias_strings.append(alias) alias_candidates.append(candidate) index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) if index_matches: return [alias_candidates[ind] for ind in index_matches] return []
for variable_name, prot in all_from_module(mod).items(): if isinstance(prot, dict): if "prototype_key" not in prot: prot['prototype_key'] = variable_name.lower() prots.append((prot['prototype_key'], homogenize_prototype(prot))) # assign module path to each prototype_key for easy reference _MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots}) # make sure the prototype contains all meta info for prototype_key, prot in prots: actual_prot_key = prot.get('prototype_key', prototype_key).lower() prot.update({ "prototype_key": actual_prot_key, "prototype_desc": prot['prototype_desc'] if 'prototype_desc' in prot else mod, "prototype_locks": (prot['prototype_locks'] if 'prototype_locks' in prot else "use:all();edit:false()"), "prototype_tags": list(set(make_iter(prot.get('prototype_tags', [])) + ["module"]))}) _MODULE_PROTOTYPES[actual_prot_key] = prot # Db-based prototypes class DbPrototype(DefaultScript): """ This stores a single prototype, in an Attribute `prototype`. """ def at_script_creation(self): self.key = "empty prototype" # prototype_key self.desc = "A prototype" # prototype_desc (.tags are used for prototype_tags) self.db.prototype = {} # actual prototype
# i18n from django.utils.translation import ugettext as _ _SERVERNAME = settings.SERVERNAME _MULTISESSION_MODE = settings.MULTISESSION_MODE _IDLE_TIMEOUT = settings.IDLE_TIMEOUT _DELAY_CMD_LOGINSTART = settings.DELAY_CMD_LOGINSTART _MAX_SERVER_COMMANDS_PER_SECOND = 100.0 _MAX_SESSION_COMMANDS_PER_SECOND = 5.0 _MODEL_MAP = None # input handlers _INPUT_FUNCS = {} for modname in make_iter(settings.INPUT_FUNC_MODULES): _INPUT_FUNCS.update(callables_from_module(modname)) def delayed_import(): """ Helper method for delayed import of all needed entities. """ global _ServerSession, _AccountDB, _ServerConfig, _ScriptDB if not _ServerSession: # we allow optional arbitrary serversession class for overloading modulename, classname = settings.SERVER_SESSION_CLASS.rsplit(".", 1) _ServerSession = variable_from_module(modulename, classname) if not _AccountDB: from evennia.accounts.models import AccountDB as _AccountDB
def save_prototype(prototype): """ Create/Store a prototype persistently. Args: prototype (dict): The prototype to save. A `prototype_key` key is required. Returns: prototype (dict or None): The prototype stored using the given kwargs, None if deleting. Raises: prototypes.ValidationError: If prototype does not validate. Note: No edit/spawn locks will be checked here - if this function is called the caller is expected to have valid permissions. """ in_prototype = prototype in_prototype = homogenize_prototype(in_prototype) def _to_batchtuple(inp, *args): "build tuple suitable for batch-creation" if is_iter(inp): # already a tuple/list, use as-is return inp return (inp, ) + args prototype_key = in_prototype.get("prototype_key") if not prototype_key: raise ValidationError("Prototype requires a prototype_key") prototype_key = str(prototype_key).lower() # we can't edit a prototype defined in a module if prototype_key in _MODULE_PROTOTYPES: mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A") raise PermissionError("{} is a read-only prototype " "(defined as code in {}).".format( prototype_key, mod)) # make sure meta properties are included with defaults in_prototype["prototype_desc"] = in_prototype.get( "prototype_desc", prototype.get("prototype_desc", "")) prototype_locks = in_prototype.get( "prototype_locks", prototype.get("prototype_locks", _PROTOTYPE_FALLBACK_LOCK)) is_valid, err = validate_lockstring(prototype_locks) if not is_valid: raise ValidationError("Lock error: {}".format(err)) in_prototype["prototype_locks"] = prototype_locks prototype_tags = [ _to_batchtuple(tag, _PROTOTYPE_TAG_META_CATEGORY) for tag in make_iter( in_prototype.get("prototype_tags", prototype.get("prototype_tags", []))) ] in_prototype["prototype_tags"] = prototype_tags stored_prototype = DbPrototype.objects.filter(db_key=prototype_key) if stored_prototype: # edit existing prototype stored_prototype = stored_prototype[0] stored_prototype.desc = in_prototype["prototype_desc"] if prototype_tags: stored_prototype.tags.clear(category=PROTOTYPE_TAG_CATEGORY) stored_prototype.tags.batch_add(*in_prototype["prototype_tags"]) stored_prototype.locks.add(in_prototype["prototype_locks"]) stored_prototype.attributes.add("prototype", in_prototype) else: # create a new prototype stored_prototype = create_script( DbPrototype, key=prototype_key, desc=in_prototype["prototype_desc"], persistent=True, locks=prototype_locks, tags=in_prototype["prototype_tags"], attributes=[("prototype", in_prototype)], ) return stored_prototype.prototype