diff --git a/docs/source/Concepts/Internationalization.md b/docs/source/Concepts/Internationalization.md index 8effa9fe62..14a8012b11 100644 --- a/docs/source/Concepts/Internationalization.md +++ b/docs/source/Concepts/Internationalization.md @@ -1,88 +1,99 @@ # Internationalization - -*Internationalization* (often abbreviated *i18n* since there are 18 characters between the first "i" -and the last "n" in that word) allows Evennia's core server to return texts in other languages than -English - without anyone having to edit the source code. Take a look at the `locale` directory of -the Evennia installation, there you will find which languages are currently supported. +*Internationalization* (often abbreviated *i18n* since there are 18 characters +between the first "i" and the last "n" in that word) allows Evennia's core +server to return texts in other languages than English - without anyone having +to edit the source code. Take a look at the `locale` directory of the Evennia +installation, there you will find which languages are currently supported. ## Changing server language -Change language by adding the following to your `mygame/server/conf/settings.py` file: +Change language by adding the following to your `mygame/server/conf/settings.py` +file: ```python + USE_I18N = True LANGUAGE_CODE = 'en' + ``` -Here `'en'` should be changed to the abbreviation for one of the supported languages found in -`locale/`. Restart the server to activate i18n. The two-character international language codes are -found [here](http://www.science.co.il/Language/Codes.asp). +Here `'en'` should be changed to the abbreviation for one of the supported +languages found in `locale/`. Restart the server to activate i18n. The +two-character international language codes are found +[here](http://www.science.co.il/Language/Codes.asp). -> Windows Note: If you get errors concerning `gettext` or `xgettext` on Windows, see the [Django -documentation](https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#gettext-on-windows). A -self-installing and up-to-date version of gettext for Windows (32/64-bit) is available on -[Github](https://github.com/mlocati/gettext-iconv-windows). +> Windows Note: If you get errors concerning `gettext` or `xgettext` on Windows, +> see the +> [Django documentation](https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#gettext-on-windows). +> A self-installing and up-to-date version of gettext for Windows (32/64-bit) is +> available on [Github](https://github.com/mlocati/gettext-iconv-windows). ## Translating Evennia -> **Important Note:** Evennia offers translations of hard-coded strings in the server, things like -"Connection closed" or "Server restarted", strings that end users will see and which game devs are -not supposed to change on their own. Text you see in the log file or on the command line (like error -messages) are generally *not* translated (this is a part of Python). +```important:: -> In addition, text in default Commands and in default Typeclasses will *not* be translated by -switching *i18n* language. To translate Commands and Typeclass hooks you must overload them in your -game directory and translate their returns to the language you want. This is because from Evennia's -perspective, adding *i18n* code to commands tend to add complexity to code that is *meant* to be -changed anyway. One of the goals of Evennia is to keep the user-changeable code as clean and easy- -to-read as possible. + Evennia offers translations of hard-coded strings in the server, things like + "Connection closed" or "Server restarted", strings that end users will see and + which game devs are not supposed to change on their own. Text you see in the log + file or on the command line (like error messages) are generally *not* translated + (this is a part of Python). -If you cannot find your language in `evennia/locale/` it's because noone has translated it yet. -Alternatively you might have the language but find the translation bad ... You are welcome to help -improve the situation! + In addition, text in default Commands and in default Typeclasses will *not* be + translated by switching *i18n* language. To translate Commands and Typeclass + hooks you must overload them in your game directory and translate their returns + to the language you want. This is because from Evennia's perspective, adding + *i18n* code to commands tend to add complexity to code that is *meant* to be + changed anyway. One of the goals of Evennia is to keep the user-changeable code + as clean and easy- to-read as possible. +``` -To start a new translation you need to first have cloned the Evennia repositry with GIT and -activated a python virtualenv as described on the [Setup Quickstart](../Setup/Setup-Quickstart) page. You now -need to `cd` to the `evennia/` directory. This is *not* your created game folder but the main -Evennia library folder. If you see a folder `locale/` then you are in the right place. From here you -run: +If you cannot find your language in `evennia/locale/` it's because noone has +translated it yet. Alternatively you might have the language but find the +translation bad ... You are welcome to help improve the situation! - evennia makemessages +To start a new translation you need to first have cloned the Evennia repositry +with GIT and activated a python virtualenv as described on the [Setup +Quickstart](../Setup/Setup-Quickstart) page. + +Go to your game dir and make sure your `virtualenv` is active so the `evennia` +command is available. Then run + + evennia makemessages --locale where `` is the [two-letter locale code](http://www.science.co.il/Language/Codes.asp) -for the language you want, like 'sv' for Swedish or 'es' for Spanish. After a moment it will tell -you the language has been processed. For instance: +for the language you want to translate, like 'sv' for Swedish or 'es' for +Spanish. After a moment it will tell you the language has been processed. For +instance: - evennia makemessages sv + evennia makemessages --locale sv -If you started a new language a new folder for that language will have emerged in the `locale/` -folder. Otherwise the system will just have updated the existing translation with eventual new -strings found in the server. Running this command will not overwrite any existing strings so you can -run it as much as you want. +If you started a new language, a new folder for that language will have emerged +in the `locale/` folder. Otherwise the system will just have updated the +existing translation with eventual new strings found in the server. Running this +command will not overwrite any existing strings so you can run it as much as you +want. -> Note: in Django, the `makemessages` command prefixes the locale name by the `-l` option (`... -makemessages -l sv` for instance). This syntax is not allowed in Evennia, due to the fact that `-l` -is the option to tail log files. Hence, `makemessages` doesn't use the `-l` flag. +Next head to `locale//LC_MESSAGES` and edit the `**.po` file you +find there. You can edit this with a normal text editor but it is easiest if +you use a special po-file editor from the web (search the web for "po editor" +for many free alternatives). -Next head to `locale//LC_MESSAGES` and edit the `**.po` file you find there. You can -edit this with a normal text editor but it is easiest if you use a special po-file editor from the -web (search the web for "po editor" for many free alternatives). - -The concept of translating is simple, it's just a matter of taking the english strings you find in -the `**.po` file and add your language's translation best you can. The `**.po` format (and many -supporting editors) allow you to mark translations as "fuzzy". This tells the system (and future -translators) that you are unsure about the translation, or that you couldn't find a translation that -exactly matched the intention of the original text. Other translators will see this and might be -able to improve it later. -Finally, you need to compile your translation into a more efficient form. Do so from the `evennia` -folder -again: +The concept of translating is simple, it's just a matter of taking the english +strings you find in the `**.po` file and add your language's translation best +you can. The `**.po` format (and many supporting editors) allow you to mark +translations as "fuzzy". This tells the system (and future translators) that you +are unsure about the translation, or that you couldn't find a translation that +exactly matched the intention of the original text. Other translators will see +this and might be able to improve it later. Finally, you need to compile your +translation into a more efficient form. Do so from the `evennia` folder again: evennia compilemessages -This will go through all languages and create/update compiled files (`**.mo`) for them. This needs -to be done whenever a `**.po` file is updated. +This will go through all languages and create/update compiled files (`**.mo`) +for them. This needs to be done whenever a `**.po` file is updated. -When you are done, send the `**.po` and `*.mo` file to the Evennia developer list (or push it into -your own repository clone) so we can integrate your translation into Evennia! \ No newline at end of file +When you are done, make sure that everyone can benefit from your translation! +Make a PR against Evennia with the updated `**.po` and `*.mo` files. Less +ideally (if git is not your thing) you can also attach them to a new post in our +forums. diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 1d4e461642..9395a99b25 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -1,5 +1,5 @@ """ -Typeclass for Account objects +Typeclass for Account objects. Note that this object is primarily intended to store OOC information, not game info! This @@ -54,7 +54,8 @@ _CMDHANDLER = None # Create throttles for too many account-creations and login attempts CREATION_THROTTLE = Throttle( - name='creation', limit=settings.CREATION_THROTTLE_LIMIT, timeout=settings.CREATION_THROTTLE_TIMEOUT + name='creation', limit=settings.CREATION_THROTTLE_LIMIT, + timeout=settings.CREATION_THROTTLE_TIMEOUT ) LOGIN_THROTTLE = Throttle( name='login', limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT @@ -292,11 +293,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): raise RuntimeError("Session not found") if self.get_puppet(session) == obj: # already puppeting this object - self.msg(_("You are already puppeting this object.")) + self.msg("You are already puppeting this object.") return if not obj.access(self, "puppet"): # no access - self.msg(_("You don't have permission to puppet '{key}'.").format(key=obj.key)) + self.msg("You don't have permission to puppet '{obj.key}'.") return if obj.account: # object already puppeted @@ -312,8 +313,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): else: txt1 = f"Taking over |c{obj.name}|n from another of your sessions." txt2 = f"|c{obj.name}|n|R is now acted from another of your sessions.|n" - self.msg(_(txt1), session=session) - self.msg(_(txt2), session=obj.sessions.all()) + self.msg(txt1, session=session) + self.msg(txt2, session=obj.sessions.all()) self.unpuppet_object(obj.sessions.get()) elif obj.account.is_connected: # controlled by another account @@ -543,7 +544,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): # Update throttle if ip: - LOGIN_THROTTLE.update(ip, "Too many authentication failures.") + LOGIN_THROTTLE.update(ip, _("Too many authentication failures.")) # Try to call post-failure hook session = kwargs.get("session", None) @@ -658,8 +659,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): password (str): Password to set. Notes: - This is called by Django also when logging in; it should not be mixed up with validation, since that - would mean old passwords in the database (pre validation checks) could get invalidated. + This is called by Django also when logging in; it should not be mixed up with + validation, since that would mean old passwords in the database (pre validation checks) + could get invalidated. """ super(DefaultAccount, self).set_password(password) @@ -798,12 +800,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): ) logger.log_sec(f"Account Created: {account} (IP: {ip}).") - except Exception as e: + except Exception: errors.append( - _( - "There was an error creating the Account. If this problem persists, contact an admin." - ) - ) + _("There was an error creating the Account. " + "If this problem persists, contact an admin.")) logger.log_trace() return None, errors @@ -819,7 +819,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): # join the new account to the public channel pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) if not pchannel or not pchannel.connect(account): - string = f"New account '{account.key}' could not connect to public channel!" + string = "New account '{account.key}' could not connect to public channel!" errors.append(string) logger.log_err(string) @@ -1574,7 +1574,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if hasattr(target, "return_appearance"): return target.return_appearance(self) else: - return _("{target} has no in-game appearance.").format(target=target) + return f"{target} has no in-game appearance." else: # list of targets - make list to disconnect from db characters = list(tar for tar in target if tar) if target else [] @@ -1617,19 +1617,18 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if is_su or len(characters) < charmax: if not characters: result.append( - _( - "\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one." - ) + "\n\n You don't have any characters yet. See |whelp charcreate|n for " + "creating one." ) else: - result.append("\n |w@charcreate [=description]|n - create new character") + result.append("\n |wcharcreate [=description]|n - create new character") result.append( - "\n |w@chardelete |n - delete a character (cannot be undone!)" + "\n |wchardelete |n - delete a character (cannot be undone!)" ) if characters: string_s_ending = len(characters) > 1 and "s" or "" - result.append("\n |w@ic |n - enter the game (|w@ooc|n to get back here)") + result.append("\n |wic |n - enter the game (|wooc|n to get back here)") if is_su: result.append( f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):" @@ -1651,11 +1650,12 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): sid = sess in sessions and sessions.index(sess) + 1 if sess and sid: result.append( - f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] (played by you in session {sid})" - ) + f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] " + f"(played by you in session {sid})") else: result.append( - f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] (played by someone else)" + f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] " + "(played by someone else)" ) else: # character is "free to puppet" @@ -1668,6 +1668,7 @@ class DefaultGuest(DefaultAccount): """ This class is used for guest logins. Unlike Accounts, Guests and their characters are deleted after disconnection. + """ @classmethod @@ -1675,6 +1676,7 @@ class DefaultGuest(DefaultAccount): """ Forwards request to cls.authenticate(); returns a DefaultGuest object if one is available for use. + """ return cls.authenticate(**kwargs) @@ -1742,7 +1744,7 @@ class DefaultGuest(DefaultAccount): return account, errors - except Exception as e: + except Exception: # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, # we won't see any errors at all. diff --git a/evennia/accounts/bots.py b/evennia/accounts/bots.py index 9989367f6f..ea22af230d 100644 --- a/evennia/accounts/bots.py +++ b/evennia/accounts/bots.py @@ -330,7 +330,7 @@ class IRCBot(Bot): nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower())) for obj in self._nicklist_callers: obj.msg( - _("Nicks at {chstr}:\n {nicklist}").format(chstr=chstr, nicklist=nicklist) + "Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist) ) self._nicklist_callers = [] return @@ -341,7 +341,7 @@ class IRCBot(Bot): chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})" for obj in self._ping_callers: obj.msg( - _("IRC ping return from {chstr} took {time}s.").format( + "IRC ping return from {chstr} took {time}s.".format( chstr=chstr, time=kwargs["timing"] ) ) @@ -397,6 +397,7 @@ class IRCBot(Bot): # # RSS +# class RSSBot(Bot): diff --git a/evennia/accounts/models.py b/evennia/accounts/models.py index 0d879e6ab7..c9cf774410 100644 --- a/evennia/accounts/models.py +++ b/evennia/accounts/models.py @@ -94,7 +94,8 @@ class AccountDB(TypedObject, AbstractUser): "cmdset", max_length=255, null=True, - help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.", + help_text="optional python path to a cmdset class. If creating a Character, this will " + "default to settings.CMDSET_CHARACTER.", ) # marks if this is a "virtual" bot account object db_is_bot = models.BooleanField( diff --git a/evennia/accounts/tests.py b/evennia/accounts/tests.py index 2abde675b7..699c65d93c 100644 --- a/evennia/accounts/tests.py +++ b/evennia/accounts/tests.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import sys from mock import Mock, MagicMock, patch from random import randint from unittest import TestCase @@ -12,8 +11,6 @@ from evennia.utils.test_resources import EvenniaTest from evennia.utils import create from evennia.utils.utils import uses_database -from django.conf import settings - class TestAccountSessionHandler(TestCase): "Check AccountSessionHandler class" diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 2ed8ac4d4a..6a84f82867 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -80,50 +80,50 @@ _SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit # is the normal "production message to echo to the account. _ERROR_UNTRAPPED = ( - """ + _(""" An untrapped error occurred. -""", - """ +"""), + _(""" An untrapped error occurred. Please file a bug report detailing the steps to reproduce. -""", +"""), ) _ERROR_CMDSETS = ( - """ + _(""" A cmdset merger-error occurred. This is often due to a syntax error in one of the cmdsets to merge. -""", - """ +"""), + _(""" A cmdset merger-error occurred. Please file a bug report detailing the steps to reproduce. -""", +"""), ) _ERROR_NOCMDSETS = ( - """ + _(""" No command sets found! This is a critical bug that can have multiple causes. -""", - """ +"""), + _(""" No command sets found! This is a sign of a critical bug. If disconnecting/reconnecting doesn't" solve the problem, try to contact the server admin through" some other means for assistance. -""", +"""), ) _ERROR_CMDHANDLER = ( - """ + _(""" A command handler bug occurred. If this is not due to a local change, please file a bug report with the Evennia project, including the traceback and steps to reproduce. -""", - """ +"""), + _(""" A command handler bug occurred. Please notify staff - they should likely file a bug report with the Evennia project. -""", +"""), ) -_ERROR_RECURSION_LIMIT = ( +_ERROR_RECURSION_LIMIT = _( "Command recursion limit ({recursion_limit}) " "reached for '{raw_cmdname}' ({cmdclass})." ) @@ -146,7 +146,7 @@ def _msg_err(receiver, stringtuple): production string (with a timestamp) to be shown to the user. """ - string = "{traceback}\n{errmsg}\n(Traceback was logged {timestamp})." + string = _("{traceback}\n{errmsg}\n(Traceback was logged {timestamp}).") timestamp = logger.timeformat() tracestring = format_exc() logger.log_trace() @@ -299,6 +299,7 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) def _get_local_obj_cmdsets(obj): """ Helper-method; Get Object-level cmdsets + """ # Gather cmdsets from location, objects in location or carried try: @@ -352,6 +353,7 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) """ Helper method; Get cmdset while making sure to trigger all hooks safely. Returns the stack and the valid options. + """ try: yield obj.at_cmdset_get() @@ -384,13 +386,6 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet" ] cmdsets += local_obj_cmdsets - # if not current.no_channels: - # # also objs may have channels - # channel_cmdsets = yield _get_channel_cmdset(obj) - # cmdsets += channel_cmdsets - # if not current.no_channels: - # channel_cmdsets = yield _get_channel_cmdset(account) - # cmdsets += channel_cmdsets elif callertype == "account": # we are calling the command from the account level @@ -408,11 +403,6 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet" ] cmdsets += local_obj_cmdsets - # if not current.no_channels: - # # also objs may have channels - # cmdsets += yield _get_channel_cmdset(obj) - # if not current.no_channels: - # cmdsets += yield _get_channel_cmdset(account) elif callertype == "object": # we are calling the command from the object level @@ -426,9 +416,6 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet" ] cmdsets += yield local_obj_cmdsets - # if not current.no_channels: - # # also objs may have channels - # cmdsets += yield _get_channel_cmdset(obj) else: raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype) diff --git a/evennia/commands/cmdset.py b/evennia/commands/cmdset.py index c2a09e069e..5f2e7d9305 100644 --- a/evennia/commands/cmdset.py +++ b/evennia/commands/cmdset.py @@ -364,8 +364,8 @@ class CmdSet(object, metaclass=_CmdSetMeta): if getattr(self, opt) is not None ]) options = (", " + options) if options else "" - return f": " + ", ".join( - [str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)]) + return (f": " + + ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])) def __iter__(self): """ @@ -477,7 +477,8 @@ class CmdSet(object, metaclass=_CmdSetMeta): # This is used for diagnosis. cmdset_c.actual_mergetype = mergetype - # print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, cmdset_a.key, cmdset_a.priority) + # print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, + # cmdset_a.key, cmdset_a.priority) # return the system commands to the cmdset cmdset_c.add(sys_commands, allow_duplicates=True) @@ -670,5 +671,6 @@ class CmdSet(object, metaclass=_CmdSetMeta): Hook method - this should be overloaded in the inheriting class, and should take care of populating the cmdset by use of self.add(). + """ pass diff --git a/evennia/commands/cmdsethandler.py b/evennia/commands/cmdsethandler.py index 23cd230913..ca204f4aa9 100644 --- a/evennia/commands/cmdsethandler.py +++ b/evennia/commands/cmdsethandler.py @@ -62,6 +62,7 @@ Since any number of CommandSets can be piled on top of each other, you can then implement separate sets for different situations. For example, you can have a 'On a boat' set, onto which you then tack on the 'Fishing' set. Fishing from a boat? No problem! + """ import sys from traceback import format_exc @@ -168,7 +169,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False): if "." in path: modpath, classname = python_path.rsplit(".", 1) else: - raise ImportError("The path '%s' is not on the form modulepath.ClassName" % path) + raise ImportError(f"The path '{path}' is not on the form modulepath.ClassName") try: # first try to get from cache @@ -312,6 +313,7 @@ class CmdSetHandler(object): def __str__(self): """ Display current commands + """ strings = [" stack:"] @@ -331,7 +333,7 @@ class CmdSetHandler(object): if mergelist: # current is a result of mergers - mergelist="+".join(mergelist) + mergelist = "+".join(mergelist) strings.append(f" : {self.current}") else: # current is a single cmdset @@ -421,7 +423,8 @@ class CmdSetHandler(object): self.mergetype_stack.append(new_current.actual_mergetype) self.current = new_current - def add(self, cmdset, emit_to_obj=None, persistent=True, permanent=True, default_cmdset=False, **kwargs): + def add(self, cmdset, emit_to_obj=None, persistent=True, default_cmdset=False, + **kwargs): """ Add a cmdset to the handler, on top of the old ones, unless it is set as the default one (it will then end up at the bottom of the stack) @@ -431,8 +434,6 @@ class CmdSetHandler(object): to such an object. emit_to_obj (Object, optional): An object to receive error messages. persistent (bool, optional): Let cmdset remain across server reload. - permanent (bool, optional): DEPRECATED. This has the same use as - `persistent`. default_cmdset (Cmdset, optional): Insert this to replace the default cmdset position (there is only one such position, always at the bottom of the stack). @@ -450,10 +451,9 @@ class CmdSetHandler(object): """ if "permanent" in kwargs: - logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed to " + logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to " "'persistent' and now defaults to True.") - - permanent = persistent or permanent + persistent = kwargs['permanent'] if persistent is None else persistent if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)): string = _("Only CmdSets can be added to the cmdsethandler!") @@ -465,8 +465,8 @@ class CmdSetHandler(object): # this is (maybe) a python path. Try to import from cache. cmdset = self._import_cmdset(cmdset) if cmdset and cmdset.key != "_CMDSET_ERROR": - cmdset.permanent = permanent - if permanent and cmdset.key != "_CMDSET_ERROR": + cmdset.permanent = persistent # TODO change on cmdset too + if persistent and cmdset.key != "_CMDSET_ERROR": # store the path permanently storage = self.obj.cmdset_storage or [""] if default_cmdset: @@ -480,17 +480,21 @@ class CmdSetHandler(object): self.cmdset_stack.append(cmdset) self.update() - def add_default(self, cmdset, emit_to_obj=None, permanent=True): + def add_default(self, cmdset, emit_to_obj=None, persistent=True, **kwargs): """ Shortcut for adding a default cmdset. Args: cmdset (Cmdset): The Cmdset to add. emit_to_obj (Object, optional): Gets error messages - permanent (bool, optional): The new Cmdset should survive a server reboot. + persistent (bool, optional): The new Cmdset should survive a server reboot. """ - self.add(cmdset, emit_to_obj=emit_to_obj, permanent=permanent, default_cmdset=True) + if "permanent" in kwargs: + logger.log_dep("obj.cmdset.add_default() kwarg 'permanent' has changed name to " + "'persistent'.") + persistent = kwargs['permanent'] if persistent is None else persistent + self.add(cmdset, emit_to_obj=emit_to_obj, persistent=persistent, default_cmdset=True) def remove(self, cmdset=None, default_cmdset=False): """ diff --git a/evennia/commands/command.py b/evennia/commands/command.py index 200bb5e5d7..b4519fc8a2 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -572,6 +572,7 @@ Command {self} has no defined `func()` - showing on-command variables: border_left_char=border_left_char, border_right_char=border_right_char, border_top_char=border_top_char, + border_bottom_char=border_bottom_char, **kwargs, ) return table diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index 7a80f03d6a..c75458fdb1 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -7,7 +7,7 @@ from django.urls import reverse from django.utils.text import slugify from evennia.typeclasses.models import TypeclassBase -from evennia.comms.models import TempMsg, ChannelDB +from evennia.comms.models import ChannelDB from evennia.comms.managers import ChannelManager from evennia.utils import create, logger from evennia.utils.utils import make_iter @@ -49,7 +49,6 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): channel_msg_nick_pattern = r"{alias}\s*?|{alias}\s+?(?P.+?)" channel_msg_nick_replacement = "channel {channelname} = $1" - def at_first_save(self): """ Called by the typeclass system the very first time the channel @@ -706,7 +705,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): """ try: return reverse("%s-create" % slugify(cls._meta.verbose_name)) - except: + except Exception: return "#" def web_get_detail_url(self): @@ -721,8 +720,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): a named view of 'channel-detail' would be referenced by this method. ex. - url(r'channels/(?P[\w\d\-]+)/$', - ChannelDetailView.as_view(), name='channel-detail') + :: + + url(r'channels/(?P[\w\d\-]+)/$', + ChannelDetailView.as_view(), name='channel-detail') If no View has been created and defined in urls.py, returns an HTML anchor. @@ -740,7 +741,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): "%s-detail" % slugify(self._meta.verbose_name), kwargs={"slug": slugify(self.db_key)}, ) - except: + except Exception: return "#" def web_get_update_url(self): @@ -755,8 +756,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): a named view of 'channel-update' would be referenced by this method. ex. - url(r'channels/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', - ChannelUpdateView.as_view(), name='channel-update') + :: + + url(r'channels/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', + ChannelUpdateView.as_view(), name='channel-update') If no View has been created and defined in urls.py, returns an HTML anchor. @@ -774,7 +777,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): "%s-update" % slugify(self._meta.verbose_name), kwargs={"slug": slugify(self.db_key)}, ) - except: + except Exception: return "#" def web_get_delete_url(self): @@ -807,7 +810,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): "%s-delete" % slugify(self._meta.verbose_name), kwargs={"slug": slugify(self.db_key)}, ) - except: + except Exception: return "#" # Used by Django Sites/Admin diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index ff076c3181..2da708af9c 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -218,7 +218,6 @@ class MsgManager(TypedObjectManager): else: raise CommError - def search_message(self, sender=None, receiver=None, freetext=None, dbref=None): """ Search the message database for particular messages. At least diff --git a/evennia/comms/models.py b/evennia/comms/models.py index fbb8bd92de..5d03b06b27 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -16,6 +16,7 @@ database. Channels are central objects that act as targets for Msgs. Accounts can connect to channels by use of a ChannelConnect object (this object is necessary to easily be able to delete connections on the fly). + """ from django.conf import settings from django.utils import timezone @@ -165,7 +166,8 @@ class Msg(SharedMemoryModel): db_tags = models.ManyToManyField( Tag, blank=True, - help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.", + help_text="tags on this message. Tags are simple string markers to " + "identify, group and alias messages.", ) # Database manager @@ -309,7 +311,6 @@ class Msg(SharedMemoryModel): self.db_receiver_external = "" self.save() - def remove_receiver(self, receivers): """ Remove a single receiver, a list of receivers, or a single extral receiver. @@ -337,8 +338,8 @@ class Msg(SharedMemoryModel): elif clsname == "ScriptDB": self.db_receivers_scripts.remove(receiver) - - def __hide_from_get(self): + @property + def hide_from(self): """ Getter. Allows for value = self.hide_from. Returns two lists of accounts and objects. @@ -349,9 +350,12 @@ class Msg(SharedMemoryModel): self.db_hide_from_objects.all(), ) - # @hide_from_sender.setter - def __hide_from_set(self, hiders): - "Setter. Allows for self.hide_from = value. Will append to hiders" + @hide_from.setter + def hide_from(self, hiders): + """ + Setter. Allows for self.hide_from = value. Will append to hiders. + + """ for hider in make_iter(hiders): if not hider: continue @@ -363,21 +367,25 @@ class Msg(SharedMemoryModel): elif clsname == "ObjectDB": self.db_hide_from_objects.add(hider.__dbclass__) - # @hide_from_sender.deleter - def __hide_from_del(self): - "Deleter. Allows for del self.hide_from_senders" + @hide_from.deleter + def hide_from(self): + """ + Deleter. Allows for del self.hide_from_senders + + """ self.db_hide_from_accounts.clear() self.db_hide_from_objects.clear() self.save() - hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del) - # # Msg class methods # def __str__(self): - "This handles what is shown when e.g. printing the message" + """ + This handles what is shown when e.g. printing the message. + + """ senders = ",".join(getattr(obj, "key", str(obj)) for obj in self.senders) receivers = ",".join(getattr(obj, "key", str(obj)) for obj in self.receivers) return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40)) @@ -407,9 +415,8 @@ class Msg(SharedMemoryModel): class TempMsg(object): """ - This is a non-persistent object for sending temporary messages - that will not be stored. It mimics the "real" Msg object, but - doesn't require sender to be given. + This is a non-persistent object for sending temporary messages that will not be stored. It + mimics the "real" Msg object, but doesn't require sender to be given. """ @@ -452,6 +459,7 @@ class TempMsg(object): def __str__(self): """ This handles what is shown when e.g. printing the message. + """ senders = ",".join(obj.key for obj in self.senders) receivers = ",".join(obj.key for obj in self.receivers) @@ -477,6 +485,7 @@ class TempMsg(object): Args: receiver (Object, Account, Script, str or list): Receivers to remove. + """ for o in make_iter(receiver): @@ -513,6 +522,7 @@ class SubscriptionHandler(object): This handler manages subscriptions to the channel and hides away which type of entity is subscribing (Account or Object) + """ def __init__(self, obj): @@ -634,7 +644,8 @@ class SubscriptionHandler(object): if not obj.is_connected: continue except ObjectDoesNotExist: - # a subscribed object has already been deleted. Mark that we need a recache and ignore it + # a subscribed object has already been deleted. Mark that we need a recache and + # ignore it recache_needed = True continue subs.append(obj) @@ -688,7 +699,7 @@ class ChannelDB(TypedObject): __defaultclasspath__ = "evennia.comms.comms.DefaultChannel" __applabel__ = "comms" - class Meta(object): + class Meta: "Define Django meta options" verbose_name = "Channel" verbose_name_plural = "Channels" diff --git a/evennia/help/filehelp.py b/evennia/help/filehelp.py index 3e56f307d6..7cbf2f2942 100644 --- a/evennia/help/filehelp.py +++ b/evennia/help/filehelp.py @@ -113,6 +113,7 @@ class FileHelpStorageHandler: Note that this is not meant to any searching/lookup - that is all handled by the help command. + """ def __init__(self, help_file_modules=settings.FILE_HELP_ENTRY_MODULES): diff --git a/evennia/help/manager.py b/evennia/help/manager.py index 398312a203..48c7f68e30 100644 --- a/evennia/help/manager.py +++ b/evennia/help/manager.py @@ -1,7 +1,6 @@ """ Custom manager for HelpEntry objects. """ -from django.db import models from evennia.utils import logger, utils from evennia.typeclasses.managers import TypedObjectManager @@ -131,7 +130,7 @@ class HelpEntryManager(TypedObjectManager): for topic in topics: topic.help_category = default_category topic.save() - string = _("Help database moved to category {default_category}").format( + string = "Help database moved to category {default_category}".format( default_category=default_category ) logger.log_info(string) diff --git a/evennia/help/models.py b/evennia/help/models.py index 63996c9f44..e9214857e8 100644 --- a/evennia/help/models.py +++ b/evennia/help/models.py @@ -168,7 +168,9 @@ class HelpEntry(SharedMemoryModel): a named view of 'character-create' would be referenced by this method. ex. - url(r'characters/create/', ChargenView.as_view(), name='character-create') + :: + + url(r'characters/create/', ChargenView.as_view(), name='character-create') If no View has been created and defined in urls.py, returns an HTML anchor. @@ -183,7 +185,7 @@ class HelpEntry(SharedMemoryModel): """ try: return reverse("%s-create" % slugify(cls._meta.verbose_name)) - except: + except Exception: return "#" def web_get_detail_url(self): @@ -198,8 +200,9 @@ class HelpEntry(SharedMemoryModel): a named view of 'character-detail' would be referenced by this method. ex. - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/$', - CharDetailView.as_view(), name='character-detail') + :: + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/$', + CharDetailView.as_view(), name='character-detail') If no View has been created and defined in urls.py, returns an HTML anchor. @@ -217,8 +220,7 @@ class HelpEntry(SharedMemoryModel): "%s-detail" % slugify(self._meta.verbose_name), kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)}, ) - except Exception as e: - print(e) + except Exception: return "#" def web_get_update_url(self): @@ -233,8 +235,10 @@ class HelpEntry(SharedMemoryModel): a named view of 'character-update' would be referenced by this method. ex. - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', - CharUpdateView.as_view(), name='character-update') + :: + + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', + CharUpdateView.as_view(), name='character-update') If no View has been created and defined in urls.py, returns an HTML anchor. @@ -252,7 +256,7 @@ class HelpEntry(SharedMemoryModel): "%s-update" % slugify(self._meta.verbose_name), kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)}, ) - except: + except Exception: return "#" def web_get_delete_url(self): @@ -266,8 +270,10 @@ class HelpEntry(SharedMemoryModel): a named view of 'character-detail' would be referenced by this method. ex. - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/delete/$', - CharDeleteView.as_view(), name='character-delete') + :: + + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/delete/$', + CharDeleteView.as_view(), name='character-delete') If no View has been created and defined in urls.py, returns an HTML anchor. @@ -285,7 +291,7 @@ class HelpEntry(SharedMemoryModel): "%s-delete" % slugify(self._meta.verbose_name), kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)}, ) - except: + except Exception: return "#" # Used by Django Sites/Admin diff --git a/evennia/help/utils.py b/evennia/help/utils.py index fce4811602..463d543a70 100644 --- a/evennia/help/utils.py +++ b/evennia/help/utils.py @@ -73,7 +73,6 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5, fields custom_stop_words_filter = stop_word_filter.generate_stop_word_filter(stop_words) _LUNR_BUILDER_PIPELINE = (trimmer, custom_stop_words_filter, stemmer) - indx = [cnd.search_index_entry for cnd in candidate_entries] mapping = {indx[ix]["key"]: cand for ix, cand in enumerate(candidate_entries)} diff --git a/evennia/locks/lockfuncs.py b/evennia/locks/lockfuncs.py index ac0a7bb6e1..99603a352b 100644 --- a/evennia/locks/lockfuncs.py +++ b/evennia/locks/lockfuncs.py @@ -11,81 +11,6 @@ Note that `accessing_obj` and `accessed_obj` can be any object type with a lock variable/field, so be careful to not expect a certain object type. - -**Appendix: MUX locks** - -Below is a list nicked from the MUX help file on the locks available -in standard MUX. Most of these are not relevant to core Evennia since -locks in Evennia are considerably more flexible and can be implemented -on an individual command/typeclass basis rather than as globally -available like the MUX ones. So many of these are not available in -basic Evennia, but could all be implemented easily if needed for the -individual game. - -``` -MUX Name: Affects: Effect: ----------------------------------------------------------------------- -DefaultLock: Exits: controls who may traverse the exit to - its destination. - Evennia: "traverse:" - Rooms: controls whether the account sees the - SUCC or FAIL message for the room - following the room description when - looking at the room. - Evennia: Custom typeclass - Accounts/Things: controls who may GET the object. - Evennia: "get:" - ParentLock: All: controls who may make @parent links to - the object. - Evennia: Typeclasses and - "puppet:" - ReceiveLock: Accounts/Things: controls who may give things to the - object. - Evennia: - SpeechLock: All but Exits: controls who may speak in that location - Evennia: - TeloutLock: All but Exits: controls who may teleport out of the - location. - Evennia: - TportLock: Rooms/Things: controls who may teleport there - Evennia: - UseLock: All but Exits: controls who may USE the object, GIVE - the object money and have the PAY - attributes run, have their messages - heard and possibly acted on by LISTEN - and AxHEAR, and invoke $-commands - stored on the object. - Evennia: Commands and Cmdsets. - DropLock: All but rooms: controls who may drop that object. - Evennia: - VisibleLock: All: Controls object visibility when the - object is not dark and the looker - passes the lock. In DARK locations, the - object must also be set LIGHT and the - viewer must pass the VisibleLock. - Evennia: Room typeclass with - Dark/light script -``` """ @@ -112,16 +37,21 @@ def _to_account(accessing_obj): def true(*args, **kwargs): - "Always returns True." - return True + """ + Always returns True. + """ + return True def all(*args, **kwargs): return True def false(*args, **kwargs): - "Always returns False" + """ + Always returns False + + """ return False @@ -129,6 +59,10 @@ def none(*args, **kwargs): return False +def superuser(*args, **kwargs): + return False + + def self(accessing_obj, accessed_obj, *args, **kwargs): """ Check if accessing_obj is the same as accessed_obj @@ -167,7 +101,7 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs): try: permission = args[0].lower() perms_object = accessing_obj.permissions.all() - except (AttributeError, IndexError) as err: + except (AttributeError, IndexError): return False gtmode = kwargs.pop("_greater_than", False) @@ -644,17 +578,6 @@ def holds(accessing_obj, accessed_obj, *args, **kwargs): return False -def superuser(*args, **kwargs): - """ - Only accepts an accesing_obj that is superuser (e.g. user #1) - - Since a superuser would not ever reach this check (superusers - bypass the lock entirely), any user who gets this far cannot be a - superuser, hence we just return False. :) - """ - return False - - def has_account(accessing_obj, accessed_obj, *args, **kwargs): """ Only returns true if accessing_obj has_account is true, that is, diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index 843a709ff4..a2cbd9e353 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -124,6 +124,7 @@ _LOCK_HANDLER = None class LockException(Exception): """ Raised during an error in a lock. + """ pass @@ -139,6 +140,7 @@ _LOCKFUNCS = {} def _cache_lockfuncs(): """ Updates the cache. + """ global _LOCKFUNCS _LOCKFUNCS = {} @@ -163,7 +165,7 @@ _RE_OK = re.compile(r"%s|and|or|not") # -class LockHandler(object): +class LockHandler: """ This handler should be attached to all objects implementing permission checks, under the property 'lockhandler'. @@ -260,16 +262,13 @@ class LockHandler(object): continue if access_type in locks: duplicates += 1 - wlist.append( - _( - "LockHandler on %(obj)s: access type '%(access_type)s' changed from '%(source)s' to '%(goal)s' " - % { - "obj": self.obj, - "access_type": access_type, - "source": locks[access_type][2], - "goal": raw_lockstring, - } - ) + wlist.append(_( + "LockHandler on {obj}: access type '{access_type}' " + "changed from '{source}' to '{goal}' ".format( + obj=self.obj, + access_type=access_type, + source=locks[access_type][2], + goal=raw_lockstring)) ) locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring) if wlist and WARNING_LOG: @@ -284,12 +283,14 @@ class LockHandler(object): def _cache_locks(self, storage_lockstring): """ Store data + """ self.locks = self._parse_lockstring(storage_lockstring) def _save_locks(self): """ Store locks to obj + """ self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()]) @@ -693,8 +694,7 @@ def check_lockstring( access_type=access_type, ) -def check_perm( - obj, permission, no_superuser_bypass=False): +def check_perm(obj, permission, no_superuser_bypass=False): """ Shortcut for checking if an object has the given `permission`. If the permission is in `settings.PERMISSION_HIERARCHY`, the check passes diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 2286f784c5..993a5d6e1c 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -2,7 +2,6 @@ Custom manager for Objects. """ import re -from itertools import chain from django.db.models import Q from django.conf import settings from django.db.models.fields import exceptions @@ -154,7 +153,8 @@ class ObjectDBManager(TypedObjectManager): Args: attribute_name (str): Attribute key to search for. - attribute_value (any): Attribute value to search for. This can also be database objects. + attribute_value (any): Attribute value to search for. This can also be database + objects. candidates (list, optional): Candidate objects to limit search to. typeclasses (list, optional): Python pats to restrict matches with. @@ -591,6 +591,7 @@ class ObjectDBManager(TypedObjectManager): """ Clear the db_sessid field of all objects having also the db_account field set. + """ self.filter(db_sessid__isnull=False).update(db_sessid=None) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index bfd34ce586..782896fb61 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -299,6 +299,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): """ Returns all exits from this object, i.e. all objects at this location having the property destination != `None`. + """ return [exi for exi in self.contents if exi.destination] @@ -345,6 +346,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): Returns: singular (str): The singular form to display. plural (str): The determined plural form of the key, including the count. + """ plural_category = "plural_key" key = kwargs.get("key", self.key) @@ -700,6 +702,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): Keyword Args: Keyword arguments will be passed to the function for all objects. + """ contents = self.contents if exclude: @@ -947,6 +950,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): """ Destroys all of the exits and any exits pointing to this object as a destination. + """ for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]: out_exit.delete() @@ -957,6 +961,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): """ Moves all objects (accounts/things) to their home location or to default home. + """ # Gather up everything that thinks this is its location. default_home_id = int(settings.DEFAULT_HOME.lstrip("#")) @@ -979,11 +984,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # If for some reason it's still None... if not home: - string = "Missing default home, '%s(#%d)' " - string += "now has a null location." obj.location = None obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin.")) - logger.log_err(string % (obj.name, obj.dbid)) + logger.log_err("Missing default home - '{name}(#{dbid})' now " + "has a null location.".format(name=obj.name, dbid=obj.dbid)) return if obj.has_account: @@ -1539,7 +1543,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): if not source_location and self.location.has_account: # This was created from nowhere and added to an account's # inventory; it's probably the result of a create command. - string = "You now have %s in your possession." % self.get_display_name(self.location) + string = _("You now have {name} in your possession.").format( + name=self.get_display_name(self.location)) self.location.msg(string) return @@ -1547,9 +1552,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): if msg: string = msg else: - string = "{object} arrives to {destination} from {origin}." + string = _("{object} arrives to {destination} from {origin}.") else: - string = "{object} arrives to {destination}." + string = _("{object} arrives to {destination}.") origin = source_location destination = self.location @@ -2157,7 +2162,7 @@ class DefaultCharacter(DefaultObject): key = cls.normalize_name(key) if not cls.validate_name(key): - errors.append("Invalid character name.") + errors.append(_("Invalid character name.")) return obj, errors # Set the supplied key as the name of the intended object @@ -2176,7 +2181,7 @@ class DefaultCharacter(DefaultObject): # Check to make sure account does not have too many chars if account: if len(account.characters) >= settings.MAX_NR_CHARACTERS: - errors.append("There are too many characters associated with this account.") + errors.append(_("There are too many characters associated with this account.")) return obj, errors # Create the Character @@ -2202,10 +2207,10 @@ class DefaultCharacter(DefaultObject): # If no description is set, set a default description if description or not obj.db.desc: - obj.db.desc = description if description else "This is a character." + obj.db.desc = description if description else _("This is a character.") except Exception as e: - errors.append("An error occurred while creating this '%s' object." % key) + errors.append(f"An error occurred while creating object '{key} object.") logger.log_err(e) return obj, errors @@ -2274,6 +2279,7 @@ class DefaultCharacter(DefaultObject): Args: account (Account): This is the connecting account. session (Session): Session controlling the connection. + """ if ( self.location is None @@ -2287,7 +2293,8 @@ class DefaultCharacter(DefaultObject): self.db.prelogout_location = self.location # save location again to be sure. else: account.msg( - "|r%s has no location and no home is set.|n" % self, session=session + _("|r{obj} has no location and no home is set.|n").format(obj=self), + session=session ) # Note to set home. def at_post_puppet(self, **kwargs): @@ -2305,11 +2312,12 @@ class DefaultCharacter(DefaultObject): puppeting this Object. """ - self.msg("\nYou become |c%s|n.\n" % self.name) + self.msg(_("\nYou become |c{name}|n.\n").format(name=self.key)) self.msg((self.at_look(self.location), {"type": "look"}), options=None) def message(obj, from_obj): - obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj) + obj.msg(_("{name} has entered the game.").format(name=self.get_display_name(obj)), + from_obj=from_obj) self.location.for_contents(message, exclude=[self], from_obj=self) @@ -2332,7 +2340,8 @@ class DefaultCharacter(DefaultObject): if self.location: def message(obj, from_obj): - obj.msg("%s has left the game." % self.get_display_name(obj), from_obj=from_obj) + obj.msg(_("{name} has left the game.").format(name=self.get_display_name(obj)), + from_obj=from_obj) self.location.for_contents(message, exclude=[self], from_obj=self) self.db.prelogout_location = self.location @@ -2343,6 +2352,7 @@ class DefaultCharacter(DefaultObject): """ Returns the idle time of the least idle session in seconds. If no sessions are connected it returns nothing. + """ idle = [session.cmd_last_visible for session in self.sessions.all()] if idle: @@ -2354,6 +2364,7 @@ class DefaultCharacter(DefaultObject): """ Returns the maximum connection time of all connected sessions in seconds. Returns nothing if there are no sessions. + """ conn = [session.conn_time for session in self.sessions.all()] if conn: @@ -2447,7 +2458,7 @@ class DefaultRoom(DefaultObject): # If no description is set, set a default description if description or not obj.db.desc: - obj.db.desc = description if description else "This is a room." + obj.db.desc = description if description else _("This is a room.") except Exception as e: errors.append("An error occurred while creating this '%s' object." % key) @@ -2653,7 +2664,7 @@ class DefaultExit(DefaultObject): # If no description is set, set a default description if description or not obj.db.desc: - obj.db.desc = description if description else "This is an exit." + obj.db.desc = description if description else _("This is an exit.") except Exception as e: errors.append("An error occurred while creating this '%s' object." % key) @@ -2750,4 +2761,4 @@ class DefaultExit(DefaultObject): read for an error string instead. """ - traversing_object.msg("You cannot go there.") + traversing_object.msg(_("You cannot go there.")) diff --git a/evennia/prototypes/protfuncs.py b/evennia/prototypes/protfuncs.py index c84ab579c8..671d231cef 100644 --- a/evennia/prototypes/protfuncs.py +++ b/evennia/prototypes/protfuncs.py @@ -51,8 +51,6 @@ def protfunc_callable_protkey(*args, **kwargs): return prot_value - - # this is picked up by FuncParser FUNCPARSER_CALLABLES = { "protkey": protfunc_callable_protkey, diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 223064ac01..dd642c5976 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -7,10 +7,10 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos import hashlib import time -from ast import literal_eval from django.conf import settings -from django.db.models import Q, Subquery +from django.db.models import Q from django.core.paginator import Paginator +from django.utils.translation import gettext as _ from evennia.scripts.scripts import DefaultScript from evennia.objects.models import ObjectDB from evennia.typeclasses.attributes import Attribute @@ -22,10 +22,6 @@ from evennia.utils.utils import ( make_iter, is_iter, dbid_to_obj, - callables_from_module, - get_all_typeclasses, - to_str, - dbref, justify, class_from_module, ) @@ -84,7 +80,6 @@ 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 @@ -211,6 +206,7 @@ def load_module_prototypes(): class DbPrototype(DefaultScript): """ This stores a single prototype, in an Attribute `prototype`. + """ def at_script_creation(self): @@ -262,7 +258,7 @@ def save_prototype(prototype): prototype_key = in_prototype.get("prototype_key") if not prototype_key: - raise ValidationError("Prototype requires a prototype_key") + raise ValidationError(_("Prototype requires a prototype_key")) prototype_key = str(prototype_key).lower() @@ -270,7 +266,8 @@ def save_prototype(prototype): 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) + _("{protkey} is a read-only prototype " "(defined as code in {module}).").format( + protkey=prototype_key, module=mod) ) # make sure meta properties are included with defaults @@ -337,20 +334,21 @@ def delete_prototype(prototype_key, caller=None): if prototype_key in _MODULE_PROTOTYPES: mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key.lower(), "N/A") raise PermissionError( - "{} is a read-only prototype " "(defined as code in {}).".format(prototype_key, mod) + _("{protkey} is a read-only prototype " "(defined as code in {module}).").format( + protkey=prototype_key, module=mod) ) stored_prototype = DbPrototype.objects.filter(db_key__iexact=prototype_key) if not stored_prototype: - raise PermissionError("Prototype {} was not found.".format(prototype_key)) + raise PermissionError(_("Prototype {} was not found.").format(prototype_key)) stored_prototype = stored_prototype[0] if caller: if not stored_prototype.access(caller, "edit"): raise PermissionError( - "{} needs explicit 'edit' permissions to " - "delete prototype {}.".format(caller, prototype_key) + _("{} needs explicit 'edit' permissions to " + "delete prototype {}.").format(caller, prototype_key) ) stored_prototype.delete() return True @@ -449,7 +447,11 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators nmodules = len(module_prototypes) ndbprots = db_matches.count() if nmodules + ndbprots != 1: - raise KeyError(f"Found {nmodules + ndbprots} matching prototypes {module_prototypes}.") + raise KeyError(_( + "Found {num} matching prototypes {module_prototypes}.").format( + num=nmodules + ndbprots, + module_prototypes=module_prototypes) + ) if return_iterators: # trying to get the entire set of prototypes - we must paginate @@ -479,10 +481,14 @@ class PrototypeEvMore(EvMore): Listing 1000+ prototypes can be very slow. So we customize EvMore to display an EvTable per paginated page rather than to try creating an EvTable for the entire dataset and then paginate it. + """ def __init__(self, caller, *args, session=None, **kwargs): - """Store some extra properties on the EvMore class""" + """ + Store some extra properties on the EvMore class + + """ self.show_non_use = kwargs.pop("show_non_use", False) self.show_non_edit = kwargs.pop("show_non_edit", False) super().__init__(caller, *args, session=session, **kwargs) @@ -493,6 +499,7 @@ class PrototypeEvMore(EvMore): and we must handle these separately since they cannot be paginated in the same way. We will build the prototypes so that the db-prototypes come first (they are likely the most volatile), followed by the mod-prototypes. + """ dbprot_query, modprot_list = inp # set the number of entries per page to half the reported height of the screen @@ -514,6 +521,7 @@ class PrototypeEvMore(EvMore): """ The listing is separated in db/mod prototypes, so we need to figure out which one to pick based on the page number. Also, pageno starts from 0. + """ dbprot_pages, modprot_list = self._data @@ -522,15 +530,16 @@ class PrototypeEvMore(EvMore): else: # get the correct slice, adjusted for the db-prototypes pageno = max(0, pageno - self._npages_db) - return modprot_list[pageno * self.height : pageno * self.height + self.height] + return modprot_list[pageno * self.height: pageno * self.height + self.height] def page_formatter(self, page): - """Input is a queryset page from django.Paginator""" + """ + Input is a queryset page from django.Paginator + + """ caller = self._caller # get use-permissions of readonly attributes (edit is always False) - display_tuples = [] - table = EvTable( "|wKey|n", "|wSpawn/Edit|n", @@ -599,7 +608,7 @@ def list_prototypes( dbprot_query, modprot_list = search_prototype(key, tags, return_iterators=True) if not dbprot_query and not modprot_list: - caller.msg("No prototypes found.", session=session) + caller.msg(_("No prototypes found."), session=session) return None # get specific prototype (one value or exception) @@ -650,7 +659,7 @@ def validate_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`.") + _flags["errors"].append(_("Prototype lacks a `prototype_key`.")) protkey = "[UNSET]" typeclass = prototype.get("typeclass") @@ -659,12 +668,13 @@ def validate_prototype( if strict and not (typeclass or prototype_parent): if is_prototype_base: _flags["errors"].append( - "Prototype {} requires `typeclass` " "or 'prototype_parent'.".format(protkey) + _("Prototype {protkey} requires `typeclass` " "or 'prototype_parent'.").format( + protkey=protkey) ) else: _flags["warnings"].append( - "Prototype {} can only be used as a mixin since it lacks " - "a typeclass or a prototype_parent.".format(protkey) + _("Prototype {protkey} can only be used as a mixin since it lacks " + "a typeclass or a prototype_parent.").format(protkey=protkey) ) if strict and typeclass: @@ -672,9 +682,9 @@ def validate_prototype( 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 - ) + _("{err}: Prototype {protkey} is based on typeclass {typeclass}, " + "which could not be imported!").format( + err=err, protkey=protkey, typeclass=typeclass) ) # recursively traverese prototype_parent chain @@ -682,19 +692,22 @@ def validate_prototype( 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)) + _flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format( + protkey=protkey)) protparent = protparents.get(protstring) if not protparent: _flags["errors"].append( - "Prototype {}'s prototype_parent '{}' was not found.".format(protkey, protstring) + _("Prototype {protkey}'s prototype_parent '{parent}' was not found.").format( + protkey=protkey, parent=protstring) ) if id(prototype) in _flags["visited"]: _flags["errors"].append( - "{} has infinite nesting of prototypes.".format(protkey or prototype) + _("{protkey} has infinite nesting of prototypes.").format( + protkey=protkey or prototype) ) if _flags["errors"]: - raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"])) + raise RuntimeError(_("Error: ") + _("\nError: ").join(_flags["errors"])) _flags["visited"].append(id(prototype)) _flags["depth"] += 1 validate_prototype( @@ -709,16 +722,16 @@ def validate_prototype( # 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) + _("Prototype {protkey} 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=protkey) ) if _flags["depth"] <= 0: if _flags["errors"]: - raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"])) + raise RuntimeError(_("Error: " + "\nError: ").join(_flags["errors"])) if _flags["warnings"]: - raise RuntimeWarning("Warning: " + "\nWarning: ".join(_flags["warnings"])) + raise RuntimeWarning(_("Warning: " + "\nWarning: ").join(_flags["warnings"])) # make sure prototype_locks are set to defaults prototype_locks = [ @@ -831,10 +844,10 @@ def prototype_to_str(prototype): category=category if category else "|wNone|n" ) out.append( - "{attrkey}{cat_locks} |c=|n {value}".format( + "{attrkey}{cat_locks}{locks} |c=|n {value}".format( attrkey=attrkey, cat_locks=cat_locks, - locks=locks if locks else "|wNone|n", + locks=" |w(locks:|n {locks})".format(locks=locks) if locks else "", value=value, ) ) diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 10bdba18d4..4c3071df2b 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -40,8 +40,8 @@ Possible keywords are: supported are 'edit' and 'use'. prototype_tags(list, optional): List of tags or tuples (tag, category) used to group prototype in listings - prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent prototype, or - a list of parents, for multiple left-to-right inheritance. + prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent + prototype, or a list of parents, for multiple left-to-right inheritance. prototype: Deprecated. Same meaning as 'parent'. typeclass (str or callable, optional): if not set, will use typeclass of parent prototype or use @@ -138,6 +138,7 @@ import hashlib import time from django.conf import settings +from django.utils.translation import gettext as _ import evennia from evennia.objects.models import ObjectDB @@ -355,8 +356,8 @@ def prototype_diff(prototype1, prototype2, maxdepth=2, homogenize=False, implici This is most useful for displaying. implicit_keep (bool, optional): If set, the resulting diff will assume KEEP unless the new prototype explicitly change them. That is, if a key exists in `prototype1` and - not in `prototype2`, it will not be REMOVEd but set to KEEP instead. This is particularly - useful for auto-generated prototypes when updating objects. + not in `prototype2`, it will not be REMOVEd but set to KEEP instead. This is + particularly useful for auto-generated prototypes when updating objects. Returns: diff (dict): A structure detailing how to convert prototype1 to prototype2. All @@ -469,8 +470,8 @@ def flatten_diff(diff): out.extend(_get_all_nested_diff_instructions(val)) else: raise RuntimeError( - "Diff contains non-dicts that are not on the " - "form (old, new, inst): {}".format(diffpart) + _("Diff contains non-dicts that are not on the " + "form (old, new, inst): {diffpart}").format(diffpart) ) return out @@ -693,11 +694,13 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None, elif key == "permissions": if directive == "REPLACE": obj.permissions.clear() - obj.permissions.batch_add(*(init_spawn_value(perm, str, caller=caller) for perm in val)) + obj.permissions.batch_add(*(init_spawn_value(perm, str, caller=caller) + for perm in val)) elif key == "aliases": if directive == "REPLACE": obj.aliases.clear() - obj.aliases.batch_add(*(init_spawn_value(alias, str, caller=caller) for alias in val)) + obj.aliases.batch_add(*(init_spawn_value(alias, str, caller=caller) + for alias in val)) elif key == "tags": if directive == "REPLACE": obj.tags.clear() @@ -923,7 +926,8 @@ def spawn(*prototypes, caller=None, **kwargs): create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, caller=caller) else: try: - create_kwargs["db_home"] = init_spawn_value(settings.DEFAULT_HOME, value_to_obj, caller=caller) + create_kwargs["db_home"] = init_spawn_value( + settings.DEFAULT_HOME, value_to_obj, caller=caller) except ObjectDB.DoesNotExist: # settings.DEFAULT_HOME not existing is common for unittests pass @@ -945,7 +949,8 @@ def spawn(*prototypes, caller=None, **kwargs): val = prot.pop("tags", []) tags = [] for (tag, category, *data) in val: - tags.append((init_spawn_value(tag, str, caller=caller), category, data[0] if data else None)) + tags.append((init_spawn_value(tag, str, caller=caller), category, data[0] + if data else None)) prototype_key = prototype.get("prototype_key", None) if prototype_key: diff --git a/evennia/scripts/monitorhandler.py b/evennia/scripts/monitorhandler.py index 67b84eaf42..1b799bfe3e 100644 --- a/evennia/scripts/monitorhandler.py +++ b/evennia/scripts/monitorhandler.py @@ -27,11 +27,13 @@ class MonitorHandler(object): """ This is a resource singleton that allows for registering callbacks for when a field or Attribute is updated (saved). + """ def __init__(self): """ Initialize the handler. + """ self.savekey = "_monitorhandler_save" self.monitors = defaultdict(lambda: defaultdict(dict)) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index ab1b29666d..cc23624e1d 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -62,22 +62,28 @@ class TaskHandlerTask: return TASK_HANDLER.get_deferred(self.task_id) def pause(self): - """Pause the callback of a task. - To resume use TaskHandlerTask.unpause + """ + Pause the callback of a task. + To resume use `TaskHandlerTask.unpause`. + """ d = self.deferred if d: d.pause() def unpause(self): - """Unpause a task, run the task if it has passed delay time.""" + """ + Unpause a task, run the task if it has passed delay time. + + """ d = self.deferred if d: d.unpause() @property def paused(self): - """A task attribute to check if the deferred instance of a task has been paused. + """ + A task attribute to check if the deferred instance of a task has been paused. This exists to mock usage of a twisted deferred object. @@ -93,7 +99,8 @@ class TaskHandlerTask: return None def do_task(self): - """Execute the task (call its callback). + """ + Execute the task (call its callback). If calling before timedelay, cancel the deferred instance affliated to this task. Remove the task from the dictionary of current tasks on a successful callback. @@ -106,7 +113,8 @@ class TaskHandlerTask: return TASK_HANDLER.do_task(self.task_id) def call(self): - """Call the callback of a task. + """ + Call the callback of a task. Leave the task unaffected otherwise. This does not use the task's deferred instance. The only requirement is that the task exist in task handler. @@ -173,7 +181,8 @@ class TaskHandlerTask: return None def exists(self): - """Check if a task exists. + """ + Check if a task exists. Most task handler methods check for existence for you. Returns: @@ -183,7 +192,8 @@ class TaskHandlerTask: return TASK_HANDLER.exists(self.task_id) def get_id(self): - """ Returns the global id for this task. For use with + """ + Returns the global id for this task. For use with `evennia.scripts.taskhandler.TASK_HANDLER`. Returns: @@ -215,7 +225,7 @@ class TaskHandler(object): self.clock = reactor # number of seconds before an uncalled canceled task is removed from TaskHandler self.stale_timeout = 60 - self._now = False # used in unit testing to manually set now time + self._now = False # used in unit testing to manually set now time def load(self): """Load from the ServerConfig. @@ -271,7 +281,10 @@ class TaskHandler(object): return True def save(self): - """Save the tasks in ServerConfig.""" + """ + Save the tasks in ServerConfig. + + """ for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): if task_id in self.to_save: @@ -286,14 +299,12 @@ class TaskHandler(object): callback = (obj, name) # Check if callback can be pickled. args and kwargs have been checked - safe_callback = None - - self.to_save[task_id] = dbserialize((date, callback, args, kwargs)) ServerConfig.objects.conf("delayed_tasks", self.to_save) def add(self, timedelay, callback, *args, **kwargs): - """Add a new task. + """ + Add a new task. If the persistent kwarg is truthy: The callback, args and values for kwarg will be serialized. Type @@ -399,7 +410,8 @@ class TaskHandler(object): return TaskHandlerTask(task_id) def exists(self, task_id): - """Check if a task exists. + """ + Check if a task exists. Most task handler methods check for existence for you. Args: @@ -415,7 +427,8 @@ class TaskHandler(object): return False def active(self, task_id): - """Check if a task is active (has not been called yet). + """ + Check if a task is active (has not been called yet). Args: task_id (int): an existing task ID. @@ -433,7 +446,8 @@ class TaskHandler(object): return False def cancel(self, task_id): - """Stop a task from automatically executing. + """ + Stop a task from automatically executing. This will not remove the task. Args: @@ -459,7 +473,8 @@ class TaskHandler(object): return False def remove(self, task_id): - """Remove a task without executing it. + """ + Remove a task without executing it. Deletes the instance of the task's deferred. Args: @@ -485,8 +500,8 @@ class TaskHandler(object): return True def clear(self, save=True, cancel=True): - """clear all tasks. - By default tasks are canceled and removed from the database also. + """ + Clear all tasks. By default tasks are canceled and removed from the database as well. Args: save=True (bool): Should changes to persistent tasks be saved to database. @@ -508,7 +523,8 @@ class TaskHandler(object): return True def call_task(self, task_id): - """Call the callback of a task. + """ + Call the callback of a task. Leave the task unaffected otherwise. This does not use the task's deferred instance. The only requirement is that the task exist in task handler. @@ -528,7 +544,8 @@ class TaskHandler(object): return callback(*args, **kwargs) def do_task(self, task_id): - """Execute the task (call its callback). + """ + Execute the task (call its callback). If calling before timedelay cancel the deferred instance affliated to this task. Remove the task from the dictionary of current tasks on a successful callback. @@ -573,7 +590,8 @@ class TaskHandler(object): return None def create_delays(self): - """Create the delayed tasks for the persistent tasks. + """ + Create the delayed tasks for the persistent tasks. This method should be automatically called when Evennia starts. """ diff --git a/evennia/server/models.py b/evennia/server/models.py index d455c54bc5..6aee55b2d8 100644 --- a/evennia/server/models.py +++ b/evennia/server/models.py @@ -9,8 +9,6 @@ manager's conf() method. """ from django.db import models -from django.urls import reverse -from django.contrib.contenttypes.models import ContentType from evennia.utils.idmapper.models import WeakSharedMemoryModel from evennia.utils import logger, utils diff --git a/evennia/server/portal/amp.py b/evennia/server/portal/amp.py index b86117c658..c75db6c8eb 100644 --- a/evennia/server/portal/amp.py +++ b/evennia/server/portal/amp.py @@ -93,7 +93,10 @@ def loads(data): def _get_logger(): - "Delay import of logger until absolutely necessary" + """ + Delay import of logger until absolutely necessary + + """ global _LOGGER if not _LOGGER: from evennia.utils import logger as _LOGGER @@ -102,7 +105,10 @@ def _get_logger(): @wraps def catch_traceback(func): - "Helper decorator" + """ + Helper decorator + + """ def decorator(*args, **kwargs): try: @@ -353,6 +359,7 @@ class AMPMultiConnectionProtocol(amp.AMP): def dataReceived(self, data): """ Handle non-AMP messages, such as HTTP communication. + """ # print("dataReceived: {}".format(data)) if data[:1] == NUL: @@ -413,6 +420,7 @@ class AMPMultiConnectionProtocol(amp.AMP): that is irrelevant. If a true connection error happens, the portal will continuously try to reconnect, showing the problem that way. + """ # print("ConnectionLost: {}: {}".format(self, reason)) try: @@ -422,20 +430,20 @@ class AMPMultiConnectionProtocol(amp.AMP): # Error handling - def errback(self, e, info): + def errback(self, err, info): """ Error callback. Handles errors to avoid dropping connections on server tracebacks. Args: - e (Failure): Deferred error instance. + err (Failure): Deferred error instance. info (str): Error string. """ - e.trap(Exception) + err.trap(Exception) _get_logger().log_err( "AMP Error from {info}: {trcbck} {err}".format( - info=info, trcbck=e.getTraceback(), err=e.getErrorMessage() + info=info, trcbck=err.getTraceback(), err=err.getErrorMessage() ) ) diff --git a/evennia/server/portal/amp_server.py b/evennia/server/portal/amp_server.py index eae1838e99..0f53dbf259 100644 --- a/evennia/server/portal/amp_server.py +++ b/evennia/server/portal/amp_server.py @@ -43,7 +43,10 @@ class AMPServerFactory(protocol.ServerFactory): noisy = False def logPrefix(self): - "How this is named in logs" + """ + How this is named in logs + + """ return "AMP" def __init__(self, portal): diff --git a/evennia/server/portal/grapevine.py b/evennia/server/portal/grapevine.py index de707a14c3..3c26552ca5 100644 --- a/evennia/server/portal/grapevine.py +++ b/evennia/server/portal/grapevine.py @@ -335,7 +335,7 @@ class GrapevineClient(WebSocketClientProtocol, Session): # incoming broadcast from network payload = data["payload"] - print("channels/broadcast:", payload["channel"], self.channel) + # print("channels/broadcast:", payload["channel"], self.channel) if str(payload["channel"]) != self.channel: # only echo from channels this particular bot actually listens to return diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index f301349ab2..82dc6ed6d4 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -183,6 +183,7 @@ class Portal(object): Returns: server_twistd_cmd (list): An instruction for starting the server, to pass to Popen. + """ server_twistd_cmd = [ "twistd", @@ -196,7 +197,10 @@ class Portal(object): return server_twistd_cmd def get_info_dict(self): - "Return the Portal info, for display." + """ + Return the Portal info, for display. + + """ return INFO_DICT def shutdown(self, _reactor_stopping=False, _stop_server=False): @@ -354,7 +358,8 @@ if SSH_ENABLED: for port in SSH_PORTS: pstring = "%s:%s" % (ifacestr, port) factory = ssh.makeFactory( - {"protocolFactory": _ssh_protocol, "protocolArgs": (), "sessions": PORTAL_SESSIONS,} + {"protocolFactory": _ssh_protocol, + "protocolArgs": (), "sessions": PORTAL_SESSIONS} ) factory.noisy = False ssh_service = internet.TCPServer(port, factory, interface=interface) @@ -390,7 +395,7 @@ if WEBSERVER_ENABLED: if WEBSOCKET_CLIENT_ENABLED and not websocket_started: # start websocket client port for the webclient # we only support one websocket client - from evennia.server.portal import webclient + from evennia.server.portal import webclient # noqa from autobahn.twisted.websocket import WebSocketServerFactory w_interface = WEBSOCKET_CLIENT_INTERFACE @@ -417,10 +422,13 @@ if WEBSERVER_ENABLED: if WEB_PLUGINS_MODULE: try: web_root = WEB_PLUGINS_MODULE.at_webproxy_root_creation(web_root) - except Exception as e: # Legacy user has not added an at_webproxy_root_creation function in existing web plugins file + except Exception: + # Legacy user has not added an at_webproxy_root_creation function in existing + # web plugins file INFO_DICT["errors"] = ( - "WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() not found - " - "copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf." + "WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() " + "not found copy 'evennia/game_template/server/conf/web_plugins.py to " + "mygame/server/conf." ) web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE) web_root.is_portal = True @@ -435,4 +443,3 @@ for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES: # external plugin services to start if plugin_module: plugin_module.start_plugin_services(PORTAL) - diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 447dc2bef9..5053add9c3 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -1,5 +1,6 @@ """ -Sessionhandler for portal sessions +Sessionhandler for portal sessions. + """ @@ -11,6 +12,7 @@ from evennia.server.sessionhandler import SessionHandler from evennia.server.portal.amp import PCONN, PDISCONN, PCONNSYNC, PDISCONNALL from evennia.utils.logger import log_trace from evennia.utils.utils import class_from_module +from django.utils.translation import gettext as _ # module import _MOD_IMPORT = None @@ -35,6 +37,8 @@ DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0) # Portal-SessionHandler class # ------------------------------------------------------------- +DOS_PROTECTION_MSG = _("{servername} DoS protection is active. You are queued to connect in {num} seconds ...") + class PortalSessionHandler(SessionHandler): """ @@ -111,16 +115,12 @@ class PortalSessionHandler(SessionHandler): _CONNECTION_QUEUE.appendleft(session) if len(_CONNECTION_QUEUE) > 1: session.data_out( - text=[ - [ - "%s DoS protection is active. You are queued to connect in %g seconds ..." - % ( - settings.SERVERNAME, - len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS, - ) - ], + text=( + (DOS_PROTECTION_MSG.format( + servername=settings.SERVERNAME, + num=len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS),), {}, - ] + ) ) now = time.time() if ( @@ -220,6 +220,7 @@ class PortalSessionHandler(SessionHandler): def disconnect_all(self): """ Disconnect all sessions, informing the Server. + """ def _callback(result, sessionhandler): @@ -478,5 +479,4 @@ class PortalSessionHandler(SessionHandler): _PORTAL_SESSION_HANDLER_CLASS = class_from_module(settings.PORTAL_SESSION_HANDLER_CLASS) - PORTAL_SESSIONS = _PORTAL_SESSION_HANDLER_CLASS() diff --git a/evennia/server/portal/rss.py b/evennia/server/portal/rss.py index c07593e6a7..51f9c8677a 100644 --- a/evennia/server/portal/rss.py +++ b/evennia/server/portal/rss.py @@ -145,6 +145,7 @@ class RSSBotFactory(object): def start(self): """ Called by portalsessionhandler. Starts the bot. + """ def errback(fail): diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index 80aa8c8f43..fd039ddeae 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -61,24 +61,25 @@ CTRL_D = "\x04" CTRL_BACKSLASH = "\x1c" CTRL_L = "\x0c" -_NO_AUTOGEN = """ +_NO_AUTOGEN = f""" Evennia could not generate SSH private- and public keys ({{err}}) Using conch default keys instead. If this error persists, create the keys manually (using the tools for your OS) and put them here: - {} - {} -""".format( - _PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE -) + {_PRIVATE_KEY_FILE} + {_PUBLIC_KEY_FILE} +""" _BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS) # not used atm class SSHServerFactory(protocol.ServerFactory): - "This is only to name this better in logs" + """ + This is only to name this better in logs + + """ noisy = False def logPrefix(self): diff --git a/evennia/server/portal/ssl.py b/evennia/server/portal/ssl.py index ce58fdf778..9aca1b29ce 100644 --- a/evennia/server/portal/ssl.py +++ b/evennia/server/portal/ssl.py @@ -50,6 +50,7 @@ class SSLProtocol(_TELNET_PROTOCOL_CLASS): """ Communication is the same as telnet, except data transfer is done with encryption. + """ def __init__(self, *args, **kwargs): @@ -62,6 +63,7 @@ def verify_SSL_key_and_cert(keyfile, certfile): This function looks for RSA key and certificate in the current directory. If files ssl.key and ssl.cert does not exist, they are created. + """ if not (os.path.exists(keyfile) and os.path.exists(certfile)): @@ -74,10 +76,11 @@ def verify_SSL_key_and_cert(keyfile, certfile): try: # create the RSA key and store it. - KEY_LENGTH = 1024 - rsaKey = Key(RSA.generate(KEY_LENGTH)) - keyString = rsaKey.toString(type="OPENSSH") - file(keyfile, "w+b").write(keyString) + KEY_LENGTH = 2048 + rsa_key = Key(RSA.generate(KEY_LENGTH)) + key_string = rsa_key.toString(type="OPENSSH") + with open(keyfile, "w+b") as fil: + fil.write(key_string) except Exception as err: print(NO_AUTOGEN.format(err=err, keyfile=keyfile)) sys.exit(5) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index e227c9a2aa..87fa126d0e 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -59,7 +59,10 @@ _BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS) class TelnetServerFactory(protocol.ServerFactory): - "This is only to name this better in logs" + """ + This exists only to name this better in logs. + + """ noisy = False def logPrefix(self): @@ -71,6 +74,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): Each player connecting over telnet (ie using most traditional mud clients) gets a telnet protocol instance assigned to them. All communication between game and player goes through here. + """ def __init__(self, *args, **kwargs): @@ -81,6 +85,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): """ Unused by default, but a good place to put debug printouts of incoming data. + """ # print(f"telnet dataReceived: {data}") try: @@ -145,11 +150,15 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): Client refuses do(linemode). This is common for MUD-specific clients, but we must ask for the sake of raw telnet. We ignore this error. + """ pass def _send_nop_keepalive(self): - """Send NOP keepalive unless flag is set""" + """ + Send NOP keepalive unless flag is set + + """ if self.protocol_flags.get("NOPKEEPALIVE"): self._write(IAC + NOP) @@ -158,7 +167,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): Allow to toggle the NOP keepalive for those sad clients that can't even handle a NOP instruction. This is turned off by the protocol_flag NOPKEEPALIVE (settable e.g. by the default - `@option` command). + `option` command). + """ if self.nop_keep_alive and self.nop_keep_alive.running: self.nop_keep_alive.stop() @@ -172,6 +182,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): When all have reported, a sync with the server is performed. The system will force-call this sync after a small time to handle clients that don't reply to handshakes at all. + """ if timeout: if self.handshakes > 0: @@ -186,6 +197,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): def at_login(self): """ Called when this session gets authenticated by the server. + """ pass @@ -321,7 +333,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): self.data_in(text=dat + b"\n") def _write(self, data): - """hook overloading the one used in plain telnet""" + """ + Hook overloading the one used in plain telnet + + """ data = data.replace(b"\n", b"\r\n").replace(b"\r\r\n", b"\r\n") super()._write(mccp_compress(self, data)) @@ -347,7 +362,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): def disconnect(self, reason=""): """ - generic hook for the engine to call in order to + Generic hook for the engine to call in order to disconnect this protocol. Args: @@ -376,6 +391,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): Keyword Args: kwargs (any): Options to the protocol + """ self.sessionhandler.data_out(self, **kwargs) @@ -442,7 +458,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): prompt = mxp_parse(prompt) prompt = to_bytes(prompt, self) prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n") - if not self.protocol_flags.get("NOPROMPTGOAHEAD", + if not self.protocol_flags.get("NOPROMPTGOAHEAD", self.protocol_flags.get("NOGOAHEAD", True)): prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) @@ -488,6 +504,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): def send_default(self, cmdname, *args, **kwargs): """ Send other oob data + """ if not cmdname == "options": self.oob.data_out(cmdname, *args, **kwargs) diff --git a/evennia/server/portal/telnet_ssl.py b/evennia/server/portal/telnet_ssl.py index 66fa606def..09b299e239 100644 --- a/evennia/server/portal/telnet_ssl.py +++ b/evennia/server/portal/telnet_ssl.py @@ -46,32 +46,29 @@ _CERTIFICATE_ISSUER = { # messages -NO_AUTOGEN = """ +NO_AUTOGEN = f""" Evennia could not auto-generate the SSL private- and public keys ({{err}}). If this error persists, create them manually (using the tools for your OS). The files should be placed and named like this: - {} - {} -""".format( - _PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE -) + {_PRIVATE_KEY_FILE} + {_PUBLIC_KEY_FILE} +""" NO_AUTOCERT = """ Evennia's could not auto-generate the SSL certificate ({{err}}). The private key already exists here: - {} + {_PRIVATE_KEY_FILE} If this error persists, create the certificate manually (using the private key and the tools for your OS). The file should be placed and named like this: - {} -""".format( - _PRIVATE_KEY_FILE, _CERTIFICATE_FILE -) + {_CERTIFICATE_FILE} +""" class SSLProtocol(TelnetProtocol): """ Communication is the same as telnet, except data transfer is done with encryption set up by the portal at start time. + """ def __init__(self, *args, **kwargs): diff --git a/evennia/server/portal/webclient.py b/evennia/server/portal/webclient.py index 0f7f05f2e7..12c5a63885 100644 --- a/evennia/server/portal/webclient.py +++ b/evennia/server/portal/webclient.py @@ -44,6 +44,7 @@ _BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS) class WebSocketClient(WebSocketServerProtocol, _BASE_SESSION_CLASS): """ Implements the server-side of the Websocket connection. + """ # nonce value, used to prevent the webclient from erasing the @@ -155,7 +156,7 @@ class WebSocketClient(WebSocketServerProtocol, _BASE_SESSION_CLASS): # in case anyone wants to expose this functionality later. # # sendClose() under autobahn/websocket/interfaces.py - ret = self.sendClose(CLOSE_NORMAL, reason) + self.sendClose(CLOSE_NORMAL, reason) def onClose(self, wasClean, code=None, reason=None): """ diff --git a/evennia/server/portal/webclient_ajax.py b/evennia/server/portal/webclient_ajax.py index 8041e5dfec..4032f68f7c 100644 --- a/evennia/server/portal/webclient_ajax.py +++ b/evennia/server/portal/webclient_ajax.py @@ -15,6 +15,7 @@ http://localhost:4001/webclient.) The WebClient resource in this module will handle these requests and act as a gateway to sessions connected over the webclient. + """ import json import re @@ -27,7 +28,7 @@ from django.utils.functional import Promise from django.conf import settings from evennia.utils.ansi import parse_ansi from evennia.utils import utils -from evennia.utils.utils import to_bytes, to_str +from evennia.utils.utils import to_bytes from evennia.utils.text2html import parse_html from evennia.server import session @@ -223,10 +224,13 @@ class AjaxWebClient(resource.Resource): return jsonify({"msg": host_string, "csessid": csessid}) def mode_keepalive(self, request): - """ This is called by render_POST when the client is replying to the keepalive. + + Args: + request (Request): Incoming request. + """ csessid = self.get_client_sessid(request) self.last_alive[csessid] = (time.time(), False) diff --git a/evennia/server/server.py b/evennia/server/server.py index 116e77566c..1c49aa70bf 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -174,6 +174,7 @@ class Evennia: The main Evennia server handler. This object sets up the database and tracks and interlinks all the twisted network services that make up evennia. + """ def __init__(self, application): @@ -243,6 +244,7 @@ class Evennia: This allows for changing default cmdset locations and default typeclasses in the settings file and have them auto-update all already existing objects. + """ global INFO_DICT @@ -471,7 +473,10 @@ class Evennia: ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime()) def get_info_dict(self): - "Return the server info, for display." + """ + Return the server info, for display. + + """ return INFO_DICT # server start/stop hooks @@ -480,6 +485,7 @@ class Evennia: """ This is called every time the server starts up, regardless of how it was shut down. + """ if SERVER_STARTSTOP_MODULE: SERVER_STARTSTOP_MODULE.at_server_start() @@ -488,6 +494,7 @@ class Evennia: """ This is called just before a server is shut down, regardless of it is fore a reload, reset or shutdown. + """ if SERVER_STARTSTOP_MODULE: SERVER_STARTSTOP_MODULE.at_server_stop() @@ -495,6 +502,7 @@ class Evennia: def at_server_reload_start(self): """ This is called only when server starts back up after a reload. + """ if SERVER_STARTSTOP_MODULE: SERVER_STARTSTOP_MODULE.at_server_reload_start() @@ -505,7 +513,7 @@ class Evennia: after reconnecting. Args: - mode (str): One of reload, reset or shutdown. + mode (str): One of 'reload', 'reset' or 'shutdown'. """ @@ -555,6 +563,7 @@ class Evennia: def at_server_reload_stop(self): """ This is called only time the server stops before a reload. + """ if SERVER_STARTSTOP_MODULE: SERVER_STARTSTOP_MODULE.at_server_reload_stop() @@ -563,6 +572,7 @@ class Evennia: """ This is called only when the server starts "cold", i.e. after a shutdown or a reset. + """ # We need to do this just in case the server was killed in a way where # the normal cleanup operations did not have time to run. @@ -590,6 +600,7 @@ class Evennia: def at_server_cold_stop(self): """ This is called only when the server goes down due to a shutdown or reset. + """ if SERVER_STARTSTOP_MODULE: SERVER_STARTSTOP_MODULE.at_server_cold_stop() diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index 33be5dd074..4fb8986f64 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -13,7 +13,6 @@ from evennia.comms.models import ChannelDB from evennia.utils import logger from evennia.utils.utils import make_iter, lazy_property, class_from_module from evennia.commands.cmdsethandler import CmdSetHandler -from evennia.server.session import Session from evennia.scripts.monitorhandler import MONITOR_HANDLER from evennia.typeclasses.attributes import AttributeHandler, InMemoryAttributeBackend, DbHolder @@ -22,9 +21,6 @@ _SA = object.__setattr__ _ObjectDB = None _ANSI = None -# i18n -from django.utils.translation import gettext as _ - _BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS) @@ -45,7 +41,10 @@ class ServerSession(_BASE_SESSION_CLASS): """ def __init__(self): - """Initiate to avoid AttributeErrors down the line""" + """ + Initiate to avoid AttributeErrors down the line + + """ self.puppet = None self.account = None self.cmdset_storage_string = "" @@ -321,7 +320,10 @@ class ServerSession(_BASE_SESSION_CLASS): self.sessionhandler.data_in(session or self, **kwargs) def __eq__(self, other): - """Handle session comparisons""" + """ + Handle session comparisons + + """ try: return self.address == other.address except AttributeError: @@ -368,6 +370,7 @@ class ServerSession(_BASE_SESSION_CLASS): def at_cmdset_get(self, **kwargs): """ A dummy hook all objects with cmdsets need to have + """ pass @@ -414,7 +417,10 @@ class ServerSession(_BASE_SESSION_CLASS): # @ndb.deleter def ndb_del(self): - """Stop accidental deletion.""" + """ + Stop accidental deletion. + + """ raise Exception("Cannot delete the ndb object!") ndb = property(ndb_get, ndb_set, ndb_del) @@ -423,5 +429,8 @@ class ServerSession(_BASE_SESSION_CLASS): # Mock access method for the session (there is no lock info # at this stage, so we just present a uniform API) def access(self, *args, **kwargs): - """Dummy method to mimic the logged-in API.""" + """ + Dummy method to mimic the logged-in API. + + """ return True diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index d5dd4f8e73..93f1cd4678 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -18,7 +18,6 @@ from django.conf import settings from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.utils.logger import log_trace from evennia.utils.utils import ( - variable_from_module, class_from_module, is_iter, make_iter, delay, @@ -29,6 +28,7 @@ from evennia.server.portal import amp from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT from codecs import decode as codecs_decode +from django.utils.translation import gettext as _ _FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED @@ -39,7 +39,7 @@ _ServerConfig = None _ScriptDB = None _OOB_HANDLER = None -_ERR_BAD_UTF8 = "Your client sent an incorrect UTF-8 sequence." +_ERR_BAD_UTF8 = _("Your client sent an incorrect UTF-8 sequence.") class DummySession(object): @@ -48,9 +48,6 @@ class DummySession(object): DUMMYSESSION = DummySession() -# i18n -from django.utils.translation import gettext as _ - _SERVERNAME = settings.SERVERNAME _MULTISESSION_MODE = settings.MULTISESSION_MODE _IDLE_TIMEOUT = settings.IDLE_TIMEOUT @@ -61,7 +58,6 @@ _MODEL_MAP = None _FUNCPARSER = None - # input handlers _INPUT_FUNCS = {} @@ -103,24 +99,36 @@ class SessionHandler(dict): """ def __getitem__(self, key): - "Clean out None-sessions automatically." + """ + Clean out None-sessions automatically. + + """ if None in self: del self[None] return super().__getitem__(key) def get(self, key, default=None): - "Clean out None-sessions automatically." + """ + Clean out None-sessions automatically. + + """ if None in self: del self[None] return super().get(key, default) def __setitem__(self, key, value): - "Don't assign None sessions" + """ + Don't assign None sessions" + + """ if key is not None: super().__setitem__(key, value) def __contains__(self, key): - "None-keys are not accepted." + """ + None-keys are not accepted. + + """ return False if key is None else super().__contains__(key) def get_sessions(self, include_unloggedin=False): @@ -158,9 +166,8 @@ class SessionHandler(dict): Args: session (Session): The relevant session instance. - kwargs (dict) Each keyword represents a send-instruction, with the keyword itself being the name - of the instruction (like "text"). Suitable values for each - keyword are: + kwargs (dict) Each keyword represents a send-instruction, with the keyword itself being + the name of the instruction (like "text"). Suitable values for each keyword are: - arg -> [[arg], {}] - [args] -> [[args], {}] - {kwargs} -> [[], {kwargs}] @@ -177,7 +184,8 @@ class SessionHandler(dict): global _FUNCPARSER if not _FUNCPARSER: from evennia.utils.funcparser import FuncParser - _FUNCPARSER = FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES, raise_errors=True) + _FUNCPARSER = FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES, + raise_errors=True) options = kwargs.pop("options", None) or {} raw = options.get("raw", False) @@ -199,7 +207,10 @@ class SessionHandler(dict): return data def _validate(data): - "Helper function to convert data to AMP-safe (picketable) values" + """ + Helper function to convert data to AMP-safe (picketable) values" + + """ if isinstance(data, dict): newdict = {} for key, part in data.items(): @@ -210,7 +221,8 @@ class SessionHandler(dict): elif isinstance(data, (str, bytes)): data = _utf8(data) - if _FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED and not raw and isinstance(self, ServerSessionHandler): + if (_FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED + and not raw and isinstance(self, ServerSessionHandler)): # only apply funcparser on the outgoing path (sessionhandler->) # data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) data = _FUNCPARSER.parse(data, strip=strip_inlinefunc, session=session) @@ -261,14 +273,11 @@ class SessionHandler(dict): class ServerSessionHandler(SessionHandler): """ - This object holds the stack of sessions active in the game at - any time. + This object holds the stack of sessions active in the game at any time. - A session register with the handler in two steps, first by - registering itself with the connect() method. This indicates an - non-authenticated session. Whenever the session is authenticated - the session together with the related account is sent to the login() - method. + A session register with the handler in two steps, first by registering itself with the connect() + method. This indicates an non-authenticated session. Whenever the session is authenticated the + session together with the related account is sent to the login() method. """ @@ -468,9 +477,8 @@ class ServerSessionHandler(SessionHandler): def login(self, session, account, force=False, testmode=False): """ - Log in the previously unloggedin session and the account we by - now should know is connected to it. After this point we assume - the session to be logged in one way or another. + Log in the previously unloggedin session and the account we by now should know is connected + to it. After this point we assume the session to be logged in one way or another. Args: session (Session): The Session to authenticate. @@ -627,7 +635,8 @@ class ServerSessionHandler(SessionHandler): # mean connecting from the same host would not catch duplicates sid = id(curr_session) doublet_sessions = [ - sess for sess in self.values() if sess.logged_in and sess.uid == uid and id(sess) != sid + sess for sess in self.values() + if sess.logged_in and sess.uid == uid and id(sess) != sid ] for session in doublet_sessions: @@ -737,8 +746,8 @@ class ServerSessionHandler(SessionHandler): puppet (Object): Object puppeted Returns. - sessions (Session or list): Can be more than one of Object is controlled by - more than one Session (MULTISESSION_MODE > 1). + sessions (Session or list): Can be more than one of Object is controlled by more than + one Session (MULTISESSION_MODE > 1). """ sessions = puppet.sessid.get() diff --git a/evennia/server/throttle.py b/evennia/server/throttle.py index 2132a429a6..65fa59d77d 100644 --- a/evennia/server/throttle.py +++ b/evennia/server/throttle.py @@ -2,9 +2,10 @@ from django.core.cache import caches from collections import deque from evennia.utils import logger import time +from django.utils.translation import gettext as _ -class Throttle(object): +class Throttle: """ Keeps a running count of failed actions per IP address. @@ -17,7 +18,7 @@ class Throttle(object): caches for automatic key eviction and persistence configurability. """ - error_msg = "Too many failed attempts; you must wait a few minutes before trying again." + error_msg = _("Too many failed attempts; you must wait a few minutes before trying again.") def __init__(self, **kwargs): """ @@ -34,7 +35,7 @@ class Throttle(object): """ try: self.storage = caches['throttle'] - except Exception as e: + except Exception: logger.log_trace("Throttle: Errors encountered; using default cache.") self.storage = caches['default'] @@ -123,7 +124,10 @@ class Throttle(object): # If this makes it engage, log a single activation event if not previously_throttled and currently_throttled: - logger.log_sec(f"Throttle Activated: {failmsg} (IP: {ip}, {self.limit} hits in {self.timeout} seconds.)") + logger.log_sec( + f"Throttle Activated: {failmsg} (IP: {ip}, " + f"{self.limit} hits in {self.timeout} seconds.)" + ) self.record_ip(ip) @@ -136,14 +140,15 @@ class Throttle(object): """ exists = self.get(ip) - if not exists: return False + if not exists: + return False cache_key = self.get_cache_key(ip) self.storage.delete(cache_key) self.unrecord_ip(ip) # Return True if NOT exists - return ~bool(self.get(ip)) + return not bool(self.get(ip)) def record_ip(self, ip, *args, **kwargs): """ diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 9009c48b75..f1bec510bd 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -10,7 +10,6 @@ which is a non-db version of Attributes. """ import re import fnmatch -import weakref from collections import defaultdict @@ -117,6 +116,7 @@ class IAttribute: class InMemoryAttribute(IAttribute): """ This Attribute is used purely for NAttributes/NAttributeHandler. It has no database backend. + """ # Primary Key has no meaning for an InMemoryAttribute. This merely serves to satisfy other code. @@ -126,11 +126,12 @@ class InMemoryAttribute(IAttribute): Create an Attribute that exists only in Memory. Args: - pk (int): This is a fake 'primary key' / id-field. It doesn't actually have to be unique, but is fed an - incrementing number from the InMemoryBackend by default. This is needed only so Attributes can be - sorted. Some parts of the API also see the lack of a .pk field as a sign that the Attribute was - deleted. + pk (int): This is a fake 'primary key' / id-field. It doesn't actually have to be + unique, but is fed an incrementing number from the InMemoryBackend by default. This + is needed only so Attributes can be sorted. Some parts of the API also see the lack + of a .pk field as a sign that the Attribute was deleted. **kwargs: Other keyword arguments are used to construct the actual Attribute. + """ self.id = pk self.pk = pk @@ -245,8 +246,8 @@ class Attribute(IAttribute, SharedMemoryModel): lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) # value property (wraps db_value) - # @property - def __value_get(self): + @property + def value(self): """ Getter. Allows for `value = self.value`. We cannot cache here since it makes certain cases (such @@ -255,8 +256,8 @@ class Attribute(IAttribute, SharedMemoryModel): """ return from_pickle(self.db_value, db_obj=self) - # @value.setter - def __value_set(self, new_value): + @value.setter + def value(self, new_value): """ Setter. Allows for self.value = value. We cannot cache here, see self.__value_get. @@ -264,14 +265,11 @@ class Attribute(IAttribute, SharedMemoryModel): self.db_value = to_pickle(new_value) self.save(update_fields=["db_value"]) - # @value.deleter - def __value_del(self): + @value.deleter + def value(self): """Deleter. Allows for del attr.value. This removes the entire attribute.""" self.delete() - value = property(__value_get, __value_set, __value_del) - - # # Handlers making use of the Attribute model # @@ -348,7 +346,7 @@ class IAttributeBackend: def _get_cache_key(self, key, category): """ - + Fetch cache key. Args: key (str): The key of the Attribute being searched for. @@ -529,7 +527,8 @@ class IAttributeBackend: def create_attribute(self, key, category, lockstring, value, strvalue=False, cache=True): """ - Creates Attribute (using the class specified for the backend), (optionally) caches it, and returns it. + Creates Attribute (using the class specified for the backend), (optionally) caches it, and + returns it. This MUST actively save the Attribute to whatever database backend is used, AND call self.set_cache(key, category, new_attrobj) @@ -714,7 +713,8 @@ class IAttributeBackend: ] ) else: - # have to cast the results to a list or we'll get a RuntimeError for removing from the dict we're iterating + # have to cast the results to a list or we'll get a RuntimeError for removing from the + # dict we're iterating self.do_batch_delete(list(attrs)) self.reset_cache() @@ -735,10 +735,10 @@ class IAttributeBackend: class InMemoryAttributeBackend(IAttributeBackend): """ - This Backend for Attributes stores NOTHING in the database. Everything is kept in memory, and normally lost - on a crash, reload, shared memory flush, etc. It generates IDs for the Attributes it manages, but these are - of little importance beyond sorting and satisfying the caching logic to know an Attribute hasn't been - deleted out from under the cache's nose. + This Backend for Attributes stores NOTHING in the database. Everything is kept in memory, and + normally lost on a crash, reload, shared memory flush, etc. It generates IDs for the Attributes + it manages, but these are of little importance beyond sorting and satisfying the caching logic + to know an Attribute hasn't been deleted out from under the cache's nose. """ @@ -810,7 +810,8 @@ class InMemoryAttributeBackend(IAttributeBackend): def do_delete_attribute(self, attr): """ - Removes the Attribute from local storage. Once it's out of the cache, garbage collection will handle the rest. + Removes the Attribute from local storage. Once it's out of the cache, garbage collection + will handle the rest. Args: attr (IAttribute): The attribute to delete. @@ -929,8 +930,9 @@ class AttributeHandler: Setup the AttributeHandler. Args: - obj (TypedObject): An Account, Object, Channel, ServerSession (not technically a typed object), etc. - backend_class (IAttributeBackend class): The class of the backend to use. + obj (TypedObject): An Account, Object, Channel, ServerSession (not technically a typed + object), etc. backend_class (IAttributeBackend class): The class of the backend to + use. """ self.obj = obj self.backend = backend_class(self, self._attrtype) @@ -1263,6 +1265,7 @@ class DbHolder: all = property(get_all) +# # Nick templating # @@ -1282,7 +1285,7 @@ This happens in two steps: This will be converted to the following regex: -\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) + \@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) Supported template markers (through fnmatch) * matches anything (non-greedy) -> .*? @@ -1345,7 +1348,7 @@ def initialize_nick_templates(pattern, replacement, pattern_is_regex=False): # groups. we need to split out any | - separated parts so we can # attach the line-break/ending extras all regexes require. pattern_regex_string = r"|".join( - or_part + r"(?:[\n\r]*?)\Z" + or_part + r"(?:[\n\r]*?)\Z" for or_part in _RE_OR.split(pattern)) else: diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 27ee491ec9..e1fd58ed32 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -595,13 +595,21 @@ class TypeclassManager(TypedObjectManager): Search by supplying a string with optional extra search criteria to aid the query. Args: - query (str): A search criteria that accepts extra search criteria on the + query (str): A search criteria that accepts extra search criteria on the following + forms: + + [key|alias|#dbref...] + [tag==[:category]...] + [attr==::category...] + + All three can be combined in the same query, separated by spaces. - following forms: [key|alias|#dbref...] [tag==[:category]...] [attr==::category...] - " != " != " Returns: - matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or - ==, != in tags or attributes, enclose them in quotes. + matches (queryset): A queryset result matching all queries exactly. If wanting to use + spaces or ==, != in tags or attributes, enclose them in quotes. + + Example: + house = smart_search("key=foo alias=bar tag=house:building tag=magic attr=color:red") Note: The flexibility of this method is limited by the input line format. Tag/attribute diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index 204de23b9d..440b3bfc55 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -69,6 +69,7 @@ _SA = object.__setattr__ def call_at_first_save(sender, instance, created, **kwargs): """ Receives a signal just after the object is saved. + """ if created: instance.at_first_save() @@ -77,6 +78,7 @@ def call_at_first_save(sender, instance, created, **kwargs): def remove_attributes_on_delete(sender, instance, **kwargs): """ Wipe object's Attributes when it's deleted + """ instance.db_attributes.all().delete() @@ -98,6 +100,7 @@ class TypeclassBase(SharedMemoryModelBase): Metaclass which should be set for the root of model proxies that don't define any new fields, like Object, Script etc. This is the basis for the typeclassing system. + """ def __new__(cls, name, bases, attrs): @@ -208,7 +211,8 @@ class TypedObject(SharedMemoryModel): "typeclass", max_length=255, null=True, - help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", + help_text="this defines what 'type' of entity this is. This variable holds " + "a Python path to a module with a valid Evennia Typeclass.", db_index=True, ) # Creation date. This is not changed once the object is created. @@ -217,16 +221,20 @@ class TypedObject(SharedMemoryModel): db_lock_storage = models.TextField( "locks", blank=True, - help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", + help_text="locks limit access to an entity. A lock is defined as a 'lock string' " + "on the form 'type:lockfunctions', defining what functionality is locked and " + "how to determine access. Not defining a lock means no access is granted.", ) # many2many relationships db_attributes = models.ManyToManyField( Attribute, - help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).", + help_text="attributes on this object. An attribute can hold any pickle-able " + "python object (see docs for special cases).", ) db_tags = models.ManyToManyField( Tag, - help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.", + help_text="tags on this object. Tags are simple string markers to identify, " + "group and alias objects.", ) # Database manager @@ -701,8 +709,8 @@ class TypedObject(SharedMemoryModel): # Attribute storage # - # @property db - def __db_get(self): + @property + def db(self): """ Attribute handler wrapper. Allows for the syntax @@ -725,26 +733,24 @@ class TypedObject(SharedMemoryModel): self._db_holder = DbHolder(self, "attributes") return self._db_holder - # @db.setter - def __db_set(self, value): + @db.setter + def db(self, value): "Stop accidentally replacing the db object" string = "Cannot assign directly to db object! " string += "Use db.attr=value instead." raise Exception(string) - # @db.deleter - def __db_del(self): + @db.deleter + def db(self): "Stop accidental deletion." raise Exception("Cannot delete the db object!") - db = property(__db_get, __db_set, __db_del) - # # Non-persistent (ndb) storage # - # @property ndb - def __ndb_get(self): + @property + def ndb(self): """ A non-attr_obj store (ndb: NonDataBase). Everything stored to this is guaranteed to be cleared when a server is shutdown. @@ -757,20 +763,18 @@ class TypedObject(SharedMemoryModel): self._ndb_holder = DbHolder(self, "nattrhandler", manager_name="nattributes") return self._ndb_holder - # @db.setter - def __ndb_set(self, value): + @ndb.setter + def ndb(self, value): "Stop accidentally replacing the ndb object" string = "Cannot assign directly to ndb object! " string += "Use ndb.attr=value instead." raise Exception(string) - # @db.deleter - def __ndb_del(self): + @ndb.deleter + def ndb(self): "Stop accidental deletion." raise Exception("Cannot delete the ndb object!") - ndb = property(__ndb_get, __ndb_set, __ndb_del) - def get_display_name(self, looker, **kwargs): """ Displays the name of the object in a viewer-aware manner. @@ -879,7 +883,7 @@ class TypedObject(SharedMemoryModel): """ try: return reverse("%s-create" % slugify(cls._meta.verbose_name)) - except: + except Exception: return "#" def web_get_detail_url(self): @@ -919,7 +923,7 @@ class TypedObject(SharedMemoryModel): "%s-detail" % slugify(self._meta.verbose_name), kwargs={"pk": self.pk, "slug": slugify(self.name)}, ) - except: + except Exception: return "#" def web_get_puppet_url(self): @@ -931,19 +935,17 @@ class TypedObject(SharedMemoryModel): str: URI path to object puppet page, if defined. Examples: + :: - ```python Oscar (Character) = '/characters/oscar/1/puppet/' - ``` For this to work, the developer must have defined a named view somewhere in urls.py that follows the format 'modelname-action', so in this case a named view of 'character-puppet' would be referenced by this method. + :: - ```python - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/puppet/$', - CharPuppetView.as_view(), name='character-puppet') - ``` + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/puppet/$', + CharPuppetView.as_view(), name='character-puppet') If no View has been created and defined in urls.py, returns an HTML anchor. @@ -959,7 +961,7 @@ class TypedObject(SharedMemoryModel): "%s-puppet" % slugify(self._meta.verbose_name), kwargs={"pk": self.pk, "slug": slugify(self.name)}, ) - except: + except Exception: return "#" def web_get_update_url(self): @@ -979,11 +981,10 @@ class TypedObject(SharedMemoryModel): For this to work, the developer must have defined a named view somewhere in urls.py that follows the format 'modelname-action', so in this case a named view of 'character-update' would be referenced by this method. + :: - ```python - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', CharUpdateView.as_view(), name='character-update') - ``` If no View has been created and defined in urls.py, returns an HTML anchor. @@ -999,7 +1000,7 @@ class TypedObject(SharedMemoryModel): "%s-update" % slugify(self._meta.verbose_name), kwargs={"pk": self.pk, "slug": slugify(self.name)}, ) - except: + except Exception: return "#" def web_get_delete_url(self): @@ -1019,11 +1020,10 @@ class TypedObject(SharedMemoryModel): somewhere in urls.py that follows the format 'modelname-action', so in this case a named view of 'character-detail' would be referenced by this method. + :: - ```python - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/delete/$', + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/delete/$', CharDeleteView.as_view(), name='character-delete') - ``` If no View has been created and defined in urls.py, returns an HTML anchor. @@ -1039,7 +1039,7 @@ class TypedObject(SharedMemoryModel): "%s-delete" % slugify(self._meta.verbose_name), kwargs={"pk": self.pk, "slug": slugify(self.name)}, ) - except: + except Exception: return "#" # Used by Django Sites/Admin diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index 3ea9f41ef6..f49ad3a14f 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -125,7 +125,10 @@ class TagHandler(object): self._cache_complete = False def _query_all(self): - "Get all tags for this objects" + """ + Get all tags for this object. + + """ query = { "%s__id" % self._model: self._objid, "tag__db_model": self._model, @@ -137,7 +140,10 @@ class TagHandler(object): ] def _fullcache(self): - "Cache all tags of this object" + """ + Cache all tags of this object. + + """ if not _TYPECLASS_AGGRESSIVE_CACHE: return tags = self._query_all() @@ -277,6 +283,7 @@ class TagHandler(object): def reset_cache(self): """ Reset the cache from the outside. + """ self._cache_complete = False self._cache = {} @@ -483,8 +490,9 @@ class TagHandler(object): Batch-add tags from a list of tuples. Args: - *args (tuple or str): Each argument should be a `tagstr` keys or tuple `(keystr, category)` or - `(keystr, category, data)`. It's possible to mix input types. + *args (tuple or str): Each argument should be a `tagstr` keys or tuple + `(keystr, category)` or `(keystr, category, data)`. It's possible to mix input + types. Notes: This will generate a mimimal number of self.add calls, diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index 1924cfd771..0dd2d8234c 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -20,8 +20,8 @@ Supported standards: ## Markup -ANSI colors: `r` ed, `g` reen, `y` ellow, `b` lue, `m` agenta, `c` yan, `n` ormal (no color). Capital -letters indicate the 'dark' variant. +ANSI colors: `r` ed, `g` reen, `y` ellow, `b` lue, `m` agenta, `c` yan, `n` ormal (no color). +Capital letters indicate the 'dark' variant. - `|r` fg bright red - `|R` fg dark red @@ -337,8 +337,9 @@ class ANSIParser(object): colval = 16 + (red * 36) + (green * 6) + blue return "\033[%s8;5;%sm" % (3 + int(background), colval) - # replaced since some clients (like Potato) does not accept codes with leading zeroes, see issue #1024. - # return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10) + # replaced since some clients (like Potato) does not accept codes with leading zeroes, + # see issue #1024. + # return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10) # noqa else: # xterm256 not supported, convert the rgb value to ansi instead @@ -729,7 +730,8 @@ class ANSIString(str, metaclass=ANSIMeta): """ - # A compiled Regex for the format mini-language: https://docs.python.org/3/library/string.html#formatspec + # A compiled Regex for the format mini-language: + # https://docs.python.org/3/library/string.html#formatspec re_format = re.compile( r"(?i)(?P(?P.)?(?P\<|\>|\=|\^))?(?P\+|\-| )?(?P\#)?" r"(?P0)?(?P\d+)?(?P\_|\,)?(?:\.(?P\d+))?" @@ -802,12 +804,14 @@ class ANSIString(str, metaclass=ANSIMeta): Current features supported: fill, align, width. Args: - format_spec (str): The format specification passed by f-string or str.format(). This is a string such as - "0<30" which would mean "left justify to 30, filling with zeros". The full specification can be found - at https://docs.python.org/3/library/string.html#formatspec + format_spec (str): The format specification passed by f-string or str.format(). This is + a string such as "0<30" which would mean "left justify to 30, filling with zeros". + The full specification can be found at + https://docs.python.org/3/library/string.html#formatspec Returns: ansi_str (str): The formatted ANSIString's .raw() form, for display. + """ # This calls the compiled regex stored on ANSIString's class to analyze the format spec. # It returns a dictionary. @@ -1067,7 +1071,7 @@ class ANSIString(str, metaclass=ANSIMeta): current_index = 0 result = tuple() for section in parent_result: - result += (self[current_index : current_index + len(section)],) + result += (self[current_index: current_index + len(section)],) current_index += len(section) return result @@ -1187,7 +1191,7 @@ class ANSIString(str, metaclass=ANSIMeta): start = next + bylen maxsplit -= 1 # NB. if it's already < 0, it stays < 0 - res.append(self[start : len(self)]) + res.append(self[start: len(self)]) if drop_spaces: return [part for part in res if part != ""] return res @@ -1230,7 +1234,7 @@ class ANSIString(str, metaclass=ANSIMeta): if next < 0: break # Get character codes after the index as well. - res.append(self[next + bylen : end]) + res.append(self[next + bylen: end]) end = next maxsplit -= 1 # NB. if it's already < 0, it stays < 0 @@ -1284,7 +1288,7 @@ class ANSIString(str, metaclass=ANSIMeta): ic -= 1 ir2 -= 1 rstripped = rstripped[::-1] - return ANSIString(lstripped + raw[ir1 : ir2 + 1] + rstripped) + return ANSIString(lstripped + raw[ir1: ir2 + 1] + rstripped) def lstrip(self, chars=None): """ @@ -1403,7 +1407,7 @@ class ANSIString(str, metaclass=ANSIMeta): start = None end = char._char_indexes[0] prefix = char._raw_string[start:end] - postfix = char._raw_string[end + 1 :] + postfix = char._raw_string[end + 1:] line = char._clean_string * amount code_indexes = [i for i in range(0, len(prefix))] length = len(prefix) + len(line) diff --git a/evennia/utils/batchprocessors.py b/evennia/utils/batchprocessors.py index fee83e7091..881ebb3360 100644 --- a/evennia/utils/batchprocessors.py +++ b/evennia/utils/batchprocessors.py @@ -284,7 +284,7 @@ class BatchCommandProcessor(object): try: path = match.group(1) return "\n#\n".join(self.parse_file(path)) - except IOError as err: + except IOError: raise IOError("#INSERT {} failed.".format(path)) text = _RE_INSERT.sub(replace_insert, text) diff --git a/evennia/utils/containers.py b/evennia/utils/containers.py index 8a99afe9f3..fa4faa1bfb 100644 --- a/evennia/utils/containers.py +++ b/evennia/utils/containers.py @@ -19,7 +19,7 @@ from evennia.utils import logger SCRIPTDB = None -class Container(object): +class Container: """ Base container class. A container is simply a storage object whose properties can be acquired as a property on it. This is generally @@ -156,9 +156,8 @@ class GlobalScriptContainer(Container): return new_script if ((found.interval != interval) - or (found.start_delay != start_delay) - or (found.repeats != repeats) - ): + or (found.start_delay != start_delay) + or (found.repeats != repeats)): # the setup changed found.start(interval=interval, start_delay=start_delay, repeats=repeats) if found.desc != desc: diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 369c03be61..a112c6ada4 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -29,7 +29,7 @@ except ImportError: from django.core.exceptions import ObjectDoesNotExist from django.contrib.contenttypes.models import ContentType from django.utils.safestring import SafeString -from evennia.utils.utils import uses_database, is_iter, to_str, to_bytes +from evennia.utils.utils import uses_database, is_iter, to_bytes from evennia.utils import logger __all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", "dbserialize", "dbunserialize") diff --git a/evennia/utils/eveditor.py b/evennia/utils/eveditor.py index a70677ff87..cd21652811 100644 --- a/evennia/utils/eveditor.py +++ b/evennia/utils/eveditor.py @@ -42,10 +42,11 @@ survive a reload. See the `EvEditor` class for more details. import re from django.conf import settings -from evennia import Command, CmdSet +from evennia import CmdSet from evennia.utils import is_iter, fill, dedent, logger, justify, to_str, utils from evennia.utils.ansi import raw from evennia.commands import cmdhandler +from django.utils.translation import gettext as _ # we use cmdhandler instead of evennia.syscmdkeys to # avoid some cases of loading before evennia init'd @@ -63,7 +64,7 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH # # ------------------------------------------------------------- -_HELP_TEXT = """ +_HELP_TEXT = _(""" - any non-command is appended to the end of the buffer. : - view buffer or only line(s) :: - raw-view buffer or only line(s) @@ -99,66 +100,66 @@ _HELP_TEXT = """ :fd - de-indent entire buffer or line :echo - turn echoing of the input on/off (helpful for some clients) -""" +""") -_HELP_LEGEND = """ +_HELP_LEGEND = _(""" Legend: - line number, like '5' or range, like '3:7'. - a single word, or multiple words with quotes around them. - longer string, usually not needing quotes. -""" +""") -_HELP_CODE = """ +_HELP_CODE = _(""" :! - Execute code buffer without saving :< - Decrease the level of automatic indentation for the next lines :> - Increase the level of automatic indentation for the next lines := - Switch automatic indentation on/off """.lstrip( "\n" -) +)) -_ERROR_LOADFUNC = """ +_ERROR_LOADFUNC = _(""" {error} |rBuffer load function error. Could not load initial data.|n -""" +""") -_ERROR_SAVEFUNC = """ +_ERROR_SAVEFUNC = _(""" {error} |rSave function returned an error. Buffer not saved.|n -""" +""") -_ERROR_NO_SAVEFUNC = "|rNo save function defined. Buffer cannot be saved.|n" +_ERROR_NO_SAVEFUNC = _("|rNo save function defined. Buffer cannot be saved.|n") -_MSG_SAVE_NO_CHANGE = "No changes need saving" -_DEFAULT_NO_QUITFUNC = "Exited editor." +_MSG_SAVE_NO_CHANGE = _("No changes need saving") +_DEFAULT_NO_QUITFUNC = _("Exited editor.") -_ERROR_QUITFUNC = """ +_ERROR_QUITFUNC = _(""" {error} |rQuit function gave an error. Skipping.|n -""" +""") -_ERROR_PERSISTENT_SAVING = """ +_ERROR_PERSISTENT_SAVING = _(""" {error} |rThe editor state could not be saved for persistent mode. Switching to non-persistent mode (which means the editor session won't survive an eventual server reload - so save often!)|n -""" +""") -_TRACE_PERSISTENT_SAVING = ( +_TRACE_PERSISTENT_SAVING = _( "EvEditor persistent-mode error. Commonly, this is because one or " "more of the EvEditor callbacks could not be pickled, for example " "because it's a class method or is defined inside another function." ) -_MSG_NO_UNDO = "Nothing to undo." -_MSG_NO_REDO = "Nothing to redo." -_MSG_UNDO = "Undid one step." -_MSG_REDO = "Redid one step." +_MSG_NO_UNDO = _("Nothing to undo.") +_MSG_NO_REDO = _("Nothing to redo.") +_MSG_UNDO = _("Undid one step.") +_MSG_REDO = _("Redid one step.") # ------------------------------------------------------------- # @@ -180,7 +181,10 @@ class CmdSaveYesNo(_COMMAND_DEFAULT_CLASS): help_cateogory = "LineEditor" def func(self): - """Implement the yes/no choice.""" + """ + Implement the yes/no choice. + + """ # this is only called from inside the lineeditor # so caller.ndb._lineditor must be set. @@ -195,7 +199,10 @@ class CmdSaveYesNo(_COMMAND_DEFAULT_CLASS): class SaveYesNoCmdSet(CmdSet): - """Stores the yesno question""" + """ + Stores the yesno question + + """ key = "quitsave_yesno" priority = 150 # override other cmdsets. @@ -331,6 +338,7 @@ class CmdEditorBase(_COMMAND_DEFAULT_CLASS): def _load_editor(caller): """ Load persistent editor from storage. + """ saved_options = caller.attributes.get("_eveditor_saved") saved_buffer, saved_undo = caller.attributes.get("_eveditor_buffer_temp", (None, None)) @@ -356,6 +364,7 @@ def _load_editor(caller): class CmdLineInput(CmdEditorBase): """ No command match - Inputs line of text into buffer. + """ key = _CMD_NOMATCH @@ -440,6 +449,7 @@ class CmdEditorGroup(CmdEditorBase): This command handles all the in-editor :-style commands. Since each command is small and very limited, this makes for a more efficient presentation. + """ caller = self.caller editor = caller.ndb._eveditor @@ -467,7 +477,7 @@ class CmdEditorGroup(CmdEditorBase): # Insert single colon alone on a line editor.update_buffer([":"] if lstart == 0 else linebuffer + [":"]) if echo_mode: - caller.msg("Single ':' added to buffer.") + caller.msg(_("Single ':' added to buffer.")) elif cmd == ":h": # help entry editor.display_help() @@ -482,7 +492,7 @@ class CmdEditorGroup(CmdEditorBase): # quit. If not saved, will ask if self.editor._unsaved: caller.cmdset.add(SaveYesNoCmdSet) - caller.msg("Save before quitting? |lcyes|lt[Y]|le/|lcno|ltN|le") + caller.msg(_("Save before quitting?") + " |lcyes|lt[Y]|le/|lcno|ltN|le") else: editor.quit() elif cmd == ":q!": @@ -497,24 +507,24 @@ class CmdEditorGroup(CmdEditorBase): elif cmd == ":UU": # reset buffer editor.update_buffer(editor._pristine_buffer) - caller.msg("Reverted all changes to the buffer back to original state.") + caller.msg(_("Reverted all changes to the buffer back to original state.")) elif cmd == ":dd": # :dd - delete line buf = linebuffer[:lstart] + linebuffer[lend:] editor.update_buffer(buf) - caller.msg("Deleted %s." % self.lstr) + caller.msg(_("Deleted {string}.").format(string= self.lstr)) elif cmd == ":dw": # :dw - delete word in entire buffer # :dw delete word only on line(s) if not self.arg1: - caller.msg("You must give a search word to delete.") + caller.msg(_("You must give a search word to delete.")) else: if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Removed %s for lines %i-%i." % (self.arg1, lstart + 1, lend + 1)) + caller.msg(_("Removed %s for lines %i-%i.") % (self.arg1, lstart + 1, lend + 1)) else: - caller.msg("Removed %s for %s." % (self.arg1, self.lstr)) + caller.msg(_("Removed %s for %s.") % (self.arg1, self.lstr)) sarea = "\n".join(linebuffer[lstart:lend]) sarea = re.sub(r"%s" % self.arg1.strip("'").strip('"'), "", sarea, re.MULTILINE) buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:] @@ -529,49 +539,49 @@ class CmdEditorGroup(CmdEditorBase): editor._indent = 0 if editor._persistent: caller.attributes.add("_eveditor_indent", 0) - caller.msg("Cleared %i lines from buffer." % self.nlines) + caller.msg(_("Cleared %i lines from buffer.") % self.nlines) elif cmd == ":y": # :y - yank line(s) to copy buffer cbuf = linebuffer[lstart:lend] editor._copy_buffer = cbuf - caller.msg("%s, %s yanked." % (self.lstr.capitalize(), cbuf)) + caller.msg(_("%s, %s yanked.") % (self.lstr.capitalize(), cbuf)) elif cmd == ":x": # :x - cut line to copy buffer cbuf = linebuffer[lstart:lend] editor._copy_buffer = cbuf buf = linebuffer[:lstart] + linebuffer[lend:] editor.update_buffer(buf) - caller.msg("%s, %s cut." % (self.lstr.capitalize(), cbuf)) + caller.msg(_("%s, %s cut.") % (self.lstr.capitalize(), cbuf)) elif cmd == ":p": # :p paste line(s) from copy buffer if not editor._copy_buffer: - caller.msg("Copy buffer is empty.") + caller.msg(_("Copy buffer is empty.")) else: buf = linebuffer[:lstart] + editor._copy_buffer + linebuffer[lstart:] editor.update_buffer(buf) - caller.msg("Pasted buffer %s to %s." % (editor._copy_buffer, self.lstr)) + caller.msg(_("Pasted buffer %s to %s.") % (editor._copy_buffer, self.lstr)) elif cmd == ":i": # :i - insert new line new_lines = self.args.split("\n") if not new_lines: - caller.msg("You need to enter a new line and where to insert it.") + caller.msg(_("You need to enter a new line and where to insert it.")) else: buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:] editor.update_buffer(buf) - caller.msg("Inserted %i new line(s) at %s." % (len(new_lines), self.lstr)) + caller.msg(_("Inserted %i new line(s) at %s.") % (len(new_lines), self.lstr)) elif cmd == ":r": # :r - replace lines new_lines = self.args.split("\n") if not new_lines: - caller.msg("You need to enter a replacement string.") + caller.msg(_("You need to enter a replacement string.")) else: buf = linebuffer[:lstart] + new_lines + linebuffer[lend:] editor.update_buffer(buf) - caller.msg("Replaced %i line(s) at %s." % (len(new_lines), self.lstr)) + caller.msg(_("Replaced %i line(s) at %s.") % (len(new_lines), self.lstr)) elif cmd == ":I": # :I - insert text at beginning of line(s) if not self.raw_string and not editor._codefunc: - caller.msg("You need to enter text to insert.") + caller.msg(_("You need to enter text to insert.")) else: buf = ( linebuffer[:lstart] @@ -579,11 +589,11 @@ class CmdEditorGroup(CmdEditorBase): + linebuffer[lend:] ) editor.update_buffer(buf) - caller.msg("Inserted text at beginning of %s." % self.lstr) + caller.msg(_("Inserted text at beginning of %s.") % self.lstr) elif cmd == ":A": # :A - append text after end of line(s) if not self.args: - caller.msg("You need to enter text to append.") + caller.msg(_("You need to enter text to append.")) else: buf = ( linebuffer[:lstart] @@ -591,23 +601,23 @@ class CmdEditorGroup(CmdEditorBase): + linebuffer[lend:] ) editor.update_buffer(buf) - caller.msg("Appended text to end of %s." % self.lstr) + caller.msg(_("Appended text to end of %s.") % self.lstr) elif cmd == ":s": # :s
  • - search and replace words # in entire buffer or on certain lines if not self.arg1 or not self.arg2: - caller.msg("You must give a search word and something to replace it with.") + caller.msg(_("You must give a search word and something to replace it with.")) else: if not self.linerange: lstart = 0 lend = self.cline + 1 caller.msg( - "Search-replaced %s -> %s for lines %i-%i." + _("Search-replaced %s -> %s for lines %i-%i.") % (self.arg1, self.arg2, lstart + 1, lend) ) else: caller.msg( - "Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr) + _("Search-replaced %s -> %s for %s.") % (self.arg1, self.arg2, self.lstr) ) sarea = "\n".join(linebuffer[lstart:lend]) @@ -629,9 +639,9 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Flood filled lines %i-%i." % (lstart + 1, lend)) + caller.msg(_("Flood filled lines %i-%i.") % (lstart + 1, lend)) else: - caller.msg("Flood filled %s." % self.lstr) + caller.msg(_("Flood filled %s.") % self.lstr) fbuf = "\n".join(linebuffer[lstart:lend]) fbuf = fill(fbuf, width=width) buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:] @@ -653,16 +663,17 @@ class CmdEditorGroup(CmdEditorBase): width = _DEFAULT_WIDTH if self.arg1 and self.arg1.lower() not in align_map: self.caller.msg( - "Valid justifications are [f]ull (default), [c]enter, [r]right or [l]eft" + _("Valid justifications are") + + " [f]ull (default), [c]enter, [r]right or [l]eft" ) return align = align_map[self.arg1.lower()] if self.arg1 else "f" if not self.linerange: lstart = 0 lend = self.cline + 1 - self.caller.msg("%s-justified lines %i-%i." % (align_name[align], lstart + 1, lend)) + self.caller.msg(_("%s-justified lines %i-%i.") % (align_name[align], lstart + 1, lend)) else: - self.caller.msg("%s-justified %s." % (align_name[align], self.lstr)) + self.caller.msg(_("%s-justified %s.") % (align_name[align], self.lstr)) jbuf = "\n".join(linebuffer[lstart:lend]) jbuf = justify(jbuf, width=width, align=align) buf = linebuffer[:lstart] + jbuf.split("\n") + linebuffer[lend:] @@ -673,9 +684,9 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Indented lines %i-%i." % (lstart + 1, lend)) + caller.msg(_("Indented lines %i-%i.") % (lstart + 1, lend)) else: - caller.msg("Indented %s." % self.lstr) + caller.msg(_("Indented %s.") % self.lstr) fbuf = [indent + line for line in linebuffer[lstart:lend]] buf = linebuffer[:lstart] + fbuf + linebuffer[lend:] editor.update_buffer(buf) @@ -684,9 +695,9 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1, lend)) + caller.msg(_("Removed left margin (dedented) lines %i-%i.") % (lstart + 1, lend)) else: - caller.msg("Removed left margin (dedented) %s." % self.lstr) + caller.msg(_("Removed left margin (dedented) %s.") % self.lstr) fbuf = "\n".join(linebuffer[lstart:lend]) fbuf = dedent(fbuf) buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:] @@ -694,45 +705,45 @@ class CmdEditorGroup(CmdEditorBase): elif cmd == ":echo": # set echoing on/off editor._echo_mode = not editor._echo_mode - caller.msg("Echo mode set to %s" % editor._echo_mode) + caller.msg(_("Echo mode set to %s") % editor._echo_mode) elif cmd == ":!": if editor._codefunc: editor._codefunc(caller, editor._buffer) else: - caller.msg("This command is only available in code editor mode.") + caller.msg(_("This command is only available in code editor mode.")) elif cmd == ":<": # :< if editor._codefunc: editor.decrease_indent() indent = editor._indent if indent >= 0: - caller.msg("Decreased indentation: new indentation is {}.".format(indent)) + caller.msg(_("Decreased indentation: new indentation is {}.").format(indent)) else: - caller.msg("|rManual indentation is OFF.|n Use := to turn it on.") + caller.msg(_("|rManual indentation is OFF.|n Use := to turn it on.")) else: - caller.msg("This command is only available in code editor mode.") + caller.msg(_("This command is only available in code editor mode.")) elif cmd == ":>": # :> if editor._codefunc: editor.increase_indent() indent = editor._indent if indent >= 0: - caller.msg("Increased indentation: new indentation is {}.".format(indent)) + caller.msg(_("Increased indentation: new indentation is {}.").format(indent)) else: - caller.msg("|rManual indentation is OFF.|n Use := to turn it on.") + caller.msg(_("|rManual indentation is OFF.|n Use := to turn it on.")) else: - caller.msg("This command is only available in code editor mode.") + caller.msg(_("This command is only available in code editor mode.")) elif cmd == ":=": # := if editor._codefunc: editor.swap_autoindent() indent = editor._indent if indent >= 0: - caller.msg("Auto-indentation turned on.") + caller.msg(_("Auto-indentation turned on.")) else: - caller.msg("Auto-indentation turned off.") + caller.msg(_("Auto-indentation turned off.")) else: - caller.msg("This command is only available in code editor mode.") + caller.msg(_("This command is only available in code editor mode.")) class EvEditorCmdSet(CmdSet): @@ -879,12 +890,13 @@ class EvEditor(object): def load_buffer(self): """ Load the buffer using the load function hook. + """ try: self._buffer = self._loadfunc(self._caller) if not isinstance(self._buffer, str): self._buffer = to_str(self._buffer) - self._caller.msg("|rNote: input buffer was converted to a string.|n") + self._caller.msg(_("|rNote: input buffer was converted to a string.|n")) except Exception as e: from evennia.utils import logger @@ -1021,7 +1033,7 @@ class EvEditor(object): header = ( "|n" + sep * 10 - + "Line Editor [%s]" % self._key + + _("Line Editor [%s]") % self._key + sep * (_DEFAULT_WIDTH - 24 - len(self._key)) ) footer = ( @@ -1029,7 +1041,7 @@ class EvEditor(object): + sep * 10 + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) + sep * 12 - + "(:h for help)" + + _("(:h for help)") + sep * (_DEFAULT_WIDTH - 54) ) if linenums: diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 289e84de34..ce2123f55c 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -415,7 +415,8 @@ class CmdEvMenuNode(Command): ) # don't give the session as a kwarg here, direct to original raise EvMenuError(err) # we must do this after the caller with the menu has been correctly identified since it - # can be either Account, Object or Session (in the latter case this info will be superfluous). + # can be either Account, Object or Session (in the latter case this info will be + # superfluous). caller.ndb._evmenu._session = self.session # we have a menu, use it. menu.parse_input(self.raw_string) @@ -619,7 +620,8 @@ class EvMenu: ).intersection(set(kwargs.keys())) if reserved_clash: raise RuntimeError( - f"One or more of the EvMenu `**kwargs` ({list(reserved_clash)}) is reserved by EvMenu for internal use." + f"One or more of the EvMenu `**kwargs` ({list(reserved_clash)}) " + "is reserved by EvMenu for internal use." ) for key, val in kwargs.items(): setattr(self, key, val) @@ -1262,7 +1264,7 @@ class EvMenu: table.extend([" " for i in range(nrows - nlastcol)]) # build the actual table grid - table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)] + table = [table[icol * nrows: (icol * nrows) + nrows] for icol in range(0, ncols)] # adjust the width of each column for icol in range(len(table)): @@ -1349,6 +1351,7 @@ def list_node(option_generator, select=None, pagesize=10): def _select_parser(caller, raw_string, **kwargs): """ Parse the select action + """ available_choices = kwargs.get("available_choices", []) @@ -1356,7 +1359,7 @@ def list_node(option_generator, select=None, pagesize=10): index = int(raw_string.strip()) - 1 selection = available_choices[index] except Exception: - caller.msg("|rInvalid choice.|n") + caller.msg(_("|rInvalid choice.|n")) else: if callable(select): try: @@ -1388,7 +1391,7 @@ def list_node(option_generator, select=None, pagesize=10): if option_list: nall_options = len(option_list) pages = [ - option_list[ind : ind + pagesize] for ind in range(0, nall_options, pagesize) + option_list[ind: ind + pagesize] for ind in range(0, nall_options, pagesize) ] npages = len(pages) @@ -1413,7 +1416,7 @@ def list_node(option_generator, select=None, pagesize=10): # allows us to call ourselves over and over, using different kwargs. options.append( { - "key": ("|Wcurrent|n", "c"), + "key": (_("|Wcurrent|n"), "c"), "desc": "|W({}/{})|n".format(page_index + 1, npages), "goto": (lambda caller: None, {"optionpage_index": page_index}), } @@ -1421,14 +1424,14 @@ def list_node(option_generator, select=None, pagesize=10): if page_index > 0: options.append( { - "key": ("|wp|Wrevious page|n", "p"), + "key": (_("|wp|Wrevious page|n"), "p"), "goto": (lambda caller: None, {"optionpage_index": page_index - 1}), } ) if page_index < npages - 1: options.append( { - "key": ("|wn|Wext page|n", "n"), + "key": (_("|wn|Wext page|n"), "n"), "goto": (lambda caller: None, {"optionpage_index": page_index + 1}), } ) @@ -1662,7 +1665,7 @@ class CmdYesNoQuestion(Command): inp = raw if inp in ('a', 'abort') and yes_no_question.allow_abort: - caller.msg("Aborted.") + caller.msg(_("Aborted.")) self._clean(caller) return @@ -1672,7 +1675,6 @@ class CmdYesNoQuestion(Command): kwargs = yes_no_question.kwargs kwargs['caller_session'] = self.session - ok = False if inp in ('yes', 'y'): yes_no_question.yes_callable(caller, *args, **kwargs) elif inp in ('no', 'n'): @@ -1684,9 +1686,9 @@ class CmdYesNoQuestion(Command): # cleanup self._clean(caller) - except Exception as err: + except Exception: # make sure to clean up cmdset if something goes wrong - caller.msg("|rError in ask_yes_no. Choice not confirmed (report to admin)|n") + caller.msg(_("|rError in ask_yes_no. Choice not confirmed (report to admin)|n")) logger.log_trace("Error in ask_yes_no") self._clean(caller) raise @@ -1938,7 +1940,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None): """ Validate goto-callable kwarg is on correct form. """ - if not "=" in kwarg: + if "=" not in kwarg: raise RuntimeError( f"EvMenu template error: goto-callable '{goto}' has a " f"non-kwarg argument ({kwarg}). All callables in the " @@ -1955,6 +1957,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None): def _parse_options(nodename, optiontxt, goto_callables): """ Parse option section into option dict. + """ options = [] optiontxt = optiontxt[0].strip() if optiontxt else "" @@ -2032,6 +2035,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None): def _parse(caller, menu_template, goto_callables): """ Parse the menu string format into a node tree. + """ nodetree = {} splits = _RE_NODE.split(menu_template) diff --git a/evennia/utils/evmore.py b/evennia/utils/evmore.py index 8c56ab458b..999214e34a 100644 --- a/evennia/utils/evmore.py +++ b/evennia/utils/evmore.py @@ -43,6 +43,7 @@ from evennia import Command, CmdSet from evennia.commands import cmdhandler from evennia.utils.ansi import ANSIString from evennia.utils.utils import make_iter, inherits_from, justify, dedent +from django.utils.translation import gettext as _ _CMD_NOMATCH = cmdhandler.CMD_NOMATCH _CMD_NOINPUT = cmdhandler.CMD_NOINPUT @@ -231,7 +232,7 @@ class EvMore(object): self._justify_kwargs = justify_kwargs self.exit_on_lastpage = exit_on_lastpage self.exit_cmd = exit_cmd - self._exit_msg = "Exited |wmore|n pager." + self._exit_msg = _("Exited |wmore|n pager.") self._kwargs = kwargs self._data = None @@ -354,8 +355,9 @@ class EvMore(object): """ Paginate by slice. This is done with an eye on memory efficiency (usually for querysets); to avoid fetching all objects at the same time. + """ - return self._data[pageno * self.height : pageno * self.height + self.height] + return self._data[pageno * self.height: pageno * self.height + self.height] def paginator_django(self, pageno): """ @@ -433,7 +435,7 @@ class EvMore(object): lines = text.split("\n") self._data = [ - _LBR.join(lines[i : i + self.height]) for i in range(0, len(lines), self.height) + _LBR.join(lines[i: i + self.height]) for i in range(0, len(lines), self.height) ] self._npages = len(self._data) @@ -451,13 +453,15 @@ class EvMore(object): Notes: If overridden, this method must perform the following actions: - - read and re-store `self._data` (the incoming data set) if needed for pagination to work. + - read and re-store `self._data` (the incoming data set) if needed for pagination to + work. - set `self._npages` to the total number of pages. Default is 1. - set `self._paginator` to a callable that will take a page number 1...N and return the data to display on that page (not any decorations or next/prev buttons). If only wanting to change the paginator, override `self.paginator` instead. - - set `self._page_formatter` to a callable that will receive the page from `self._paginator` - and format it with one element per line. Default is `str`. Or override `self.page_formatter` + - set `self._page_formatter` to a callable that will receive the page from + `self._paginator` and format it with one element per line. Default is `str`. Or + override `self.page_formatter` directly instead. By default, helper methods are called that perform these actions diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index ec46b42c5a..d2895336de 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -239,12 +239,12 @@ class ANSITextWrapper(TextWrapper): del chunks[-1] while chunks: - l = d_len(chunks[-1]) + ln = d_len(chunks[-1]) # Can at least squeeze this chunk onto the current line. - if cur_len + l <= width: + if cur_len + ln <= width: cur_line.append(chunks.pop()) - cur_len += l + cur_len += ln # Nope, this line is full. else: @@ -262,10 +262,10 @@ class ANSITextWrapper(TextWrapper): # Convert current line back to a string and store it in list # of all lines (return value). if cur_line: - l = "" + ln = "" for w in cur_line: # ANSI fix - l += w # - lines.append(indent + l) + ln += w # + lines.append(indent + ln) return lines @@ -1099,8 +1099,9 @@ class EvTable(object): height (int, optional): Fixed height of table. Defaults to being unset. Width is still given precedence. If given, table cells will crop text rather than expand vertically. - evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as even width as - possible. This often looks best also for mixed-length tables. Default is `False`. + evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as + even width as possible. This often looks best also for mixed-length tables. Default + is `False`. maxwidth (int, optional): This will set a maximum width of the table while allowing it to be smaller. Only if it grows wider than this size will it be resized by expanding horizontally (or crop `height` is given). @@ -1347,7 +1348,8 @@ class EvTable(object): self.ncols = ncols self.nrows = nrowmax - # add borders - these add to the width/height, so we must do this before calculating width/height + # add borders - these add to the width/height, so we must do this before calculating + # width/height self._borders() # equalize widths within each column @@ -1434,7 +1436,8 @@ class EvTable(object): except Exception: raise - # equalize heights for each row (we must do this here, since it may have changed to fit new widths) + # equalize heights for each row (we must do this here, since it may have changed to fit new + # widths) cheights = [ max(cell.get_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax) diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index 2f161578f0..31fed57b8d 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -43,11 +43,9 @@ The `FuncParser` also accepts a direct dict mapping of `{'name': callable, ...}` --- """ -import re import dataclasses import inspect import random -from functools import partial from django.conf import settings from evennia.utils import logger from evennia.utils.utils import ( @@ -234,8 +232,6 @@ class FuncParser: f"(available: {available})") return str(parsedfunc) - nargs = len(args) - # build kwargs in the proper priority order kwargs = {**self.default_kwargs, **kwargs, **reserved_kwargs, **{'funcparser': self, "raise_errors": raise_errors}} @@ -606,7 +602,7 @@ def funcparser_callable_eval(*args, **kwargs): - `$py(3 + 4) -> 7` """ - args, kwargs = safe_convert_to_types(("py", {}) , *args, **kwargs) + args, kwargs = safe_convert_to_types(("py", {}), *args, **kwargs) return args[0] if args else '' @@ -694,7 +690,7 @@ def funcparser_callable_round(*args, **kwargs): """ if not args: return '' - args, _ = safe_convert_to_types(((float, int), {}) *args, **kwargs) + args, _ = safe_convert_to_types(((float, int), {}), *args, **kwargs) num, *significant = args significant = significant[0] if significant else 0 @@ -1032,7 +1028,8 @@ def funcparser_callable_search_list(*args, caller=None, access="control", **kwar return_list=True, **kwargs) -def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, capitalize=False, **kwargs): +def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, capitalize=False, + **kwargs): """ Usage: $you() or $you(key) @@ -1081,10 +1078,12 @@ def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, cap capitalize = bool(capitalize) if caller == receiver: return "You" if capitalize else "you" - return caller.get_display_name(looker=receiver) if hasattr(caller, "get_display_name") else str(caller) + return (caller.get_display_name(looker=receiver) + if hasattr(caller, "get_display_name") else str(caller)) -def funcparser_callable_You(*args, you=None, receiver=None, mapping=None, capitalize=True, **kwargs): +def funcparser_callable_You(*args, you=None, receiver=None, mapping=None, capitalize=True, + **kwargs): """ Usage: $You() - capitalizes the 'you' output. diff --git a/evennia/utils/gametime.py b/evennia/utils/gametime.py index 0be6640618..0da66530af 100644 --- a/evennia/utils/gametime.py +++ b/evennia/utils/gametime.py @@ -7,7 +7,6 @@ total runtime of the server and the current uptime. """ import time -from calendar import monthrange from datetime import datetime, timedelta from django.db.utils import OperationalError diff --git a/evennia/utils/idmapper/models.py b/evennia/utils/idmapper/models.py index d2d46493a3..c62f3798b6 100644 --- a/evennia/utils/idmapper/models.py +++ b/evennia/utils/idmapper/models.py @@ -63,7 +63,8 @@ class SharedMemoryModelBase(ModelBase): return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs) instance_key = cls._get_cache_key(args, kwargs) - # depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance + # depending on the arguments, we might not be able to infer the PK, so in that case we + # create a new instance if instance_key is None: return new_instance() cached_instance = cls.get_cached_instance(instance_key) @@ -154,9 +155,9 @@ class SharedMemoryModelBase(ModelBase): if isinstance(value, (str, int)): value = to_str(value) if value.isdigit() or value.startswith("#"): - # we also allow setting using dbrefs, if so we try to load the matching object. - # (we assume the object is of the same type as the class holding the field, if - # not a custom handler must be used for that field) + # we also allow setting using dbrefs, if so we try to load the matching + # object. (we assume the object is of the same type as the class holding + # the field, if not a custom handler must be used for that field) dbid = dbref(value, reqhash=False) if dbid: model = _GA(cls, "_meta").get_field(fname).model @@ -266,21 +267,24 @@ class SharedMemoryModel(Model, metaclass=SharedMemoryModelBase): pk = cls._meta.pks[0] else: pk = cls._meta.pk - # get the index of the pk in the class fields. this should be calculated *once*, but isn't atm + # get the index of the pk in the class fields. this should be calculated *once*, but isn't + # atm pk_position = cls._meta.fields.index(pk) if len(args) > pk_position: # if it's in the args, we can get it easily by index result = args[pk_position] elif pk.attname in kwargs: - # retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a - # a ForeignKey. + # retrieve the pk value. Note that we use attname instead of name, to handle the case + # where the pk is a a ForeignKey. result = kwargs[pk.attname] elif pk.name != pk.attname and pk.name in kwargs: - # ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead + # ok we couldn't find the value, but maybe it's a FK and we can find the corresponding + # object instead result = kwargs[pk.name] if result is not None and isinstance(result, Model): - # if the pk value happens to be a model instance (which can happen wich a FK), we'd rather use its own pk as the key + # if the pk value happens to be a model instance (which can happen wich a FK), we'd + # rather use its own pk as the key result = result._get_pk_val() return result diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index 33a21f41f8..dc097bd299 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -16,7 +16,6 @@ log_typemsg(). This is for historical, back-compatible reasons. import os import time -import glob from datetime import datetime from traceback import format_exc from twisted.python import log, logfile @@ -47,6 +46,7 @@ def timeformat(when=None): Returns: timestring (str): A formatted string of the given time. + """ when = when if when else time.time() @@ -126,6 +126,7 @@ class WeeklyLogFile(logfile.DailyLogFile): server.log.2020_01_29 server.log.2020_01_29__1 server.log.2020_01_29__2 + """ suffix = "" copy_suffix = 0 @@ -146,7 +147,10 @@ class WeeklyLogFile(logfile.DailyLogFile): return suffix def write(self, data): - "Write data to log file" + """ + Write data to log file + + """ logfile.BaseLogFile.write(self, data) self.lastDate = max(self.lastDate, self.toDate()) self.size += len(data) @@ -155,6 +159,7 @@ class WeeklyLogFile(logfile.DailyLogFile): class PortalLogObserver(log.FileLogObserver): """ Reformat logging + """ timeFormat = None @@ -289,6 +294,7 @@ def log_info(infomsg): Prints any generic debugging/informative info that should appear in the log. infomsg: (string) The message to be logged. + """ try: infomsg = str(infomsg) @@ -307,6 +313,7 @@ def log_dep(depmsg): Args: depmsg (str): The deprecation message to log. + """ try: depmsg = str(depmsg) @@ -325,6 +332,7 @@ def log_sec(secmsg): Args: secmsg (str): The security message to log. + """ try: secmsg = str(secmsg) @@ -346,6 +354,7 @@ class EvenniaLogFile(logfile.LogFile): the LogFile's rotate method in order to append some of the last lines of the previous log to the start of the new log, in order to preserve a continuous chat history for channel log files. + """ # we delay import of settings to keep logger module as free @@ -361,6 +370,7 @@ class EvenniaLogFile(logfile.LogFile): """ Rotates our log file and appends some number of lines from the previous log to the start of the new one. + """ append_tail = (num_lines_to_append if num_lines_to_append is not None @@ -377,9 +387,11 @@ class EvenniaLogFile(logfile.LogFile): """ Convenience method for accessing our _file attribute's seek method, which is used in tail_log_function. + Args: *args: Same args as file.seek **kwargs: Same kwargs as file.seek + """ return self._file.seek(*args, **kwargs) @@ -387,12 +399,14 @@ class EvenniaLogFile(logfile.LogFile): """ Convenience method for accessing our _file attribute's readlines method, which is used in tail_log_function. + Args: *args: same args as file.readlines **kwargs: same kwargs as file.readlines Returns: lines (list): lines from our _file attribute. + """ return [line.decode("utf-8") for line in self._file.readlines(*args, **kwargs)] @@ -550,7 +564,7 @@ def tail_log_file(filename, offset, nlines, callback=None): lines_found = filehandle.readlines() block_count -= 1 # return the right number of lines - lines_found = lines_found[-nlines - offset : -offset if offset else None] + lines_found = lines_found[-nlines - offset: -offset if offset else None] if callback: callback(lines_found) return None diff --git a/evennia/utils/optionhandler.py b/evennia/utils/optionhandler.py index 243a0e36c4..fbac1c1282 100644 --- a/evennia/utils/optionhandler.py +++ b/evennia/utils/optionhandler.py @@ -1,5 +1,6 @@ from evennia.utils.utils import string_partial_matching from evennia.utils.containers import OPTION_CLASSES +from django.utils.translation import gettext as _ _GA = object.__getattribute__ _SA = object.__setattr__ @@ -134,7 +135,7 @@ class OptionHandler: """ if key not in self.options_dict: if raise_error: - raise KeyError("Option not found!") + raise KeyError(_("Option not found!")) return default # get the options or load/recache it op_found = self.options.get(key) or self._load_option(key) @@ -155,12 +156,14 @@ class OptionHandler: """ if not key: - raise ValueError("Option field blank!") + raise ValueError(_("Option field blank!")) match = string_partial_matching(list(self.options_dict.keys()), key, ret_index=False) if not match: - raise ValueError("Option not found!") + raise ValueError(_("Option not found!")) if len(match) > 1: - raise ValueError(f"Multiple matches: {', '.join(match)}. Please be more specific.") + raise ValueError(_("Multiple matches:") + + f"{', '.join(match)}. " + + _("Please be more specific.")) match = match[0] op = self.get(match, return_obj=True) op.set(value, **kwargs) diff --git a/evennia/utils/search.py b/evennia/utils/search.py index ac23d9ff05..787b81bbea 100644 --- a/evennia/utils/search.py +++ b/evennia/utils/search.py @@ -61,8 +61,7 @@ except OperationalError: from evennia.scripts.models import ScriptDB from evennia.comms.models import Msg, ChannelDB from evennia.help.models import HelpEntry - from evennia.typeclasses.tags import Tag - + from evennia.typeclasses.tags import Tag # noqa # ------------------------------------------------------------------- # Search manager-wrappers @@ -243,7 +242,7 @@ def search_script_attribute( def search_channel_attribute( key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs ): - return Channel.objects.get_by_attribute( + return ChannelDB.objects.get_by_attribute( key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs ) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index ef18f529fe..d4dada1abc 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -9,7 +9,6 @@ be of use when designing your own game. import os import gc import sys -import copy import types import math import re @@ -35,6 +34,7 @@ from django.utils.translation import gettext as _ from django.apps import apps from django.core.validators import validate_email as django_validate_email from django.core.exceptions import ValidationError as DjangoValidationError + from evennia.utils import logger _MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE @@ -204,7 +204,7 @@ def dedent(text, baseline_index=None, indent=None): baseline = lines[baseline_index] spaceremove = len(baseline) - len(baseline.lstrip(" ")) return "\n".join( - line[min(spaceremove, len(line) - len(line.lstrip(" "))) :] for line in lines + line[min(spaceremove, len(line) - len(line.lstrip(" "))):] for line in lines ) @@ -343,7 +343,7 @@ def columnize(string, columns=2, spacing=4, align="l", width=None): cols = [] istart = 0 for irows in nrows: - cols.append(onecol[istart : istart + irows]) + cols.append(onecol[istart: istart + irows]) istart = istart + irows for col in cols: if len(col) < height: @@ -1029,8 +1029,6 @@ def uses_database(name="sqlite3"): return engine == "django.db.backends.%s" % name - - def delay(timedelay, callback, *args, **kwargs): """ Delay the calling of a callback (function). @@ -1238,8 +1236,8 @@ def check_evennia_dependencies(): except ImportError: errstring += ( "\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it." - "\n Linux Debian/Ubuntu users should install package 'python-twisted-words', others" - "\n can get it from http://twistedmatrix.com/trac/wiki/TwistedWords." + "\n Linux Debian/Ubuntu users should install package 'python-twisted-words', " + "\n others can get it from http://twistedmatrix.com/trac/wiki/TwistedWords." ) not_error = False errstring = errstring.strip() @@ -1911,7 +1909,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): wl = wls[ie] lrow = len(row) - debug = row.replace(" ", ".") + # debug = row.replace(" ", ".") if lrow + wl > width: # this slot extends outside grid, move to next line @@ -1970,8 +1968,6 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None): return _weighted_rows(elements) - - def get_evennia_pids(): """ Get the currently valid PIDs (Process IDs) of the Portal and @@ -2323,7 +2319,7 @@ def get_game_dir_path(): """ # current working directory, assumed to be somewhere inside gamedir. - for _ in range(10): + for inum in range(10): gpath = os.getcwd() if "server" in os.listdir(gpath): if os.path.isfile(os.path.join("server", "conf", "settings.py")): diff --git a/evennia/utils/validatorfuncs.py b/evennia/utils/validatorfuncs.py index 29f747967f..3084b91b93 100644 --- a/evennia/utils/validatorfuncs.py +++ b/evennia/utils/validatorfuncs.py @@ -22,18 +22,20 @@ def text(entry, option_key="Text", **kwargs): try: return str(entry) except Exception as err: - raise ValueError(f"Input could not be converted to text ({err})") + raise ValueError(_("Input could not be converted to text ({err})").format(err=err)) def color(entry, option_key="Color", **kwargs): """ The color should be just a color character, so 'r' if red color is desired. + """ if not entry: - raise ValueError(f"Nothing entered for a {option_key}!") + raise ValueError(_("Nothing entered for a {option_key}!").format(option_key=option_key)) test_str = strip_ansi(f"|{entry}|n") if test_str: - raise ValueError(f"'{entry}' is not a valid {option_key}.") + raise ValueError(_("'{entry}' is not a valid {option_key}.").format( + entry=entry, option_key=option_key)) return entry @@ -83,13 +85,17 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs) entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}" else: raise ValueError( - f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}" + _("{option_key} must be entered in a 24-hour format such as: {timeformat}").format( + option_key=option_key, + timeformat=now.strftime('%b %d %H:%M')) ) try: local = _dt.datetime.strptime(entry, "%b %d %H:%M %Y") except ValueError: raise ValueError( - f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}" + _("{option_key} must be entered in a 24-hour format such as: {timeformat}").format( + option_key=option_key, + timeformat=now.strftime('%b %d %H:%M')) ) local_tz = from_tz.localize(local) return local_tz.astimezone(utc) @@ -100,8 +106,9 @@ def duration(entry, option_key="Duration", **kwargs): Take a string and derive a datetime timedelta from it. Args: - entry (string): This is a string from user-input. The intended format is, for example: "5d 2w 90s" for - 'five days, two weeks, and ninety seconds.' Invalid sections are ignored. + entry (string): This is a string from user-input. The intended format is, for example: + "5d 2w 90s" for 'five days, two weeks, and ninety seconds.' Invalid sections are + ignored. option_key (str): Name to display this query as. Returns: @@ -129,7 +136,10 @@ def duration(entry, option_key="Duration", **kwargs): elif _re.match(r"^[\d]+y$", interval): days += int(interval.rstrip("y")) * 365 else: - raise ValueError(f"Could not convert section '{interval}' to a {option_key}.") + raise ValueError( + _("Could not convert section '{interval}' to a {option_key}.").format( + interval=interval, option_key=option_key) + ) return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks) @@ -137,45 +147,56 @@ def duration(entry, option_key="Duration", **kwargs): def future(entry, option_key="Future Datetime", from_tz=None, **kwargs): time = datetime(entry, option_key, from_tz=from_tz) if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc): - raise ValueError(f"That {option_key} is in the past! Must give a Future datetime!") + raise ValueError(_("That {option_key} is in the past! Must give a Future datetime!").format( + option_key=option_key)) return time def signed_integer(entry, option_key="Signed Integer", **kwargs): if not entry: - raise ValueError(f"Must enter a whole number for {option_key}!") + raise ValueError(_("Must enter a whole number for {option_key}!").format( + option_key=option_key)) try: num = int(entry) except ValueError: - raise ValueError(f"Could not convert '{entry}' to a whole number for {option_key}!") + raise ValueError(_("Could not convert '{entry}' to a whole " + "number for {option_key}!").format( + entry=entry, option_key=option_key)) return num def positive_integer(entry, option_key="Positive Integer", **kwargs): num = signed_integer(entry, option_key) if not num >= 1: - raise ValueError(f"Must enter a whole number greater than 0 for {option_key}!") + raise ValueError(_("Must enter a whole number greater than 0 for {option_key}!").format( + option_key=option_key)) return num def unsigned_integer(entry, option_key="Unsigned Integer", **kwargs): num = signed_integer(entry, option_key) if not num >= 0: - raise ValueError(f"{option_key} must be a whole number greater than or equal to 0!") + raise ValueError(_("{option_key} must be a whole number greater than " + "or equal to 0!").format( + option_key=option_key)) return num def boolean(entry, option_key="True/False", **kwargs): """ Simplest check in computer logic, right? This will take user input to flick the switch on or off + Args: entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1. option_key (str): What kind of Boolean we are setting. What Option is this for? Returns: Boolean + """ - error = f"Must enter 0 (false) or 1 (true) for {option_key}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled" + error = (_("Must enter a true/false input for {option_key}. Accepts {alternatives}.").format( + option_key=option_key, + alternatives="0/1, True/False, On/Off, Yes/No, Enabled/Disabled")) if not isinstance(entry, str): raise ValueError(error) entry = entry.upper() @@ -196,39 +217,42 @@ def timezone(entry, option_key="Timezone", **kwargs): Returns: A PYTZ timezone. + """ if not entry: - raise ValueError(f"No {option_key} entered!") + raise ValueError(_("No {option_key} entered!").format(option_key=option_key)) found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False) if len(found) > 1: raise ValueError( - f"That matched: {', '.join(str(t) for t in found)}. Please be more specific!" - ) + _("That matched: {matches}. Please be more specific!").format( + matches=', '.join(str(t) for t in found))) if found: return _TZ_DICT[found[0]] - raise ValueError(f"Could not find timezone '{entry}' for {option_key}!") + raise ValueError(_("Could not find timezone '{entry}' for {option_key}!").format( + entry=entry, option_key=option_key)) def email(entry, option_key="Email Address", **kwargs): if not entry: - raise ValueError("Email address field empty!") + raise ValueError(_("Email address field empty!")) valid = validate_email_address(entry) if not valid: - raise ValueError(f"That isn't a valid {option_key}!") + raise ValueError(_("That isn't a valid {option_key}!").format(option_key=option_key)) return entry def lock(entry, option_key="locks", access_options=None, **kwargs): entry = entry.strip() if not entry: - raise ValueError(f"No {option_key} entered to set!") + raise ValueError(_("No {option_key} entered to set!").format(option_key=option_key)) for locksetting in entry.split(";"): access_type, lockfunc = locksetting.split(":", 1) if not access_type: - raise ValueError("Must enter an access type!") + raise ValueError(_("Must enter an access type!")) if access_options: if access_type not in access_options: - raise ValueError(f"Access type must be one of: {', '.join(access_options)}") + raise ValueError(_("Access type must be one of: {alternatives}").format( + alternatives=', '.join(access_options))) if not lockfunc: - raise ValueError("Lock func not entered.") + raise ValueError(_("Lock func not entered.")) return entry