Ran black on sources

This commit is contained in:
Griatch 2022-09-18 23:58:32 +02:00
parent 6fa68745ba
commit 43378b4c41
30 changed files with 473 additions and 275 deletions

View file

@ -7,13 +7,13 @@ import evennia.accounts.manager
class Migration(migrations.Migration):
dependencies = [
('accounts', '0009_auto_20191025_0831'),
("accounts", "0009_auto_20191025_0831"),
]
operations = [
migrations.AlterField(
model_name='accountdb',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
model_name="accountdb",
name="first_name",
field=models.CharField(blank=True, max_length=150, verbose_name="first name"),
),
]

View file

@ -41,7 +41,7 @@ PATH_REMAP_PREFIX = {
"auditing": "evennia.contrib.utils",
"fieldfill": "evennia.contrib.utils",
"random_string_generator": "evennia.contrib.utils",
"tree_select": "evennia.contrib.utils"
"tree_select": "evennia.contrib.utils",
}
@ -52,43 +52,45 @@ def convert_contrib_typeclass_paths(apps, schema_editor):
try:
package_path = obj.db_typeclass_path.split(".")[2:]
package_name = package_path[0]
if package_path[0] == 'security':
if package_path[0] == "security":
# renamed package and changed path
package_name = 'auditing'
package_name = "auditing"
package_path.pop(0) # no longer security/auditing
if package_path[-1] == ".Clothing":
# renamed Clothing class to ContribClothing
package_path[-1] = "ContribClothing"
package_path = '.'.join(package_path)
package_path = ".".join(package_path)
except IndexError:
print(f"obj.db_typeclass_path={obj.db_typeclass_path} could not be parsed "
"for converting to the new contrib location.")
print(
f"obj.db_typeclass_path={obj.db_typeclass_path} could not be parsed "
"for converting to the new contrib location."
)
continue
if package_name in PATH_REMAP_PREFIX:
obj.db_typeclass_path = f"{PATH_REMAP_PREFIX[package_name]}.{package_path}"
obj.save(update_fields=['db_typeclass_path'])
obj.save(update_fields=["db_typeclass_path"])
for obj in AccountDB.objects.filter(db_cmdset_storage__startswith="evennia.contrib."):
try:
package_path = obj.db_cmdset_storage.split(".")[2:]
package_name = package_path[0]
package_path = '.'.join(package_path)
package_path = ".".join(package_path)
except IndexError:
print(f"obj.db_cmdset_storage={obj.db_cmdset_storage} could not be parsed "
"for converting to the new contrib location.")
print(
f"obj.db_cmdset_storage={obj.db_cmdset_storage} could not be parsed "
"for converting to the new contrib location."
)
continue
if package_name in PATH_REMAP_PREFIX:
obj.db_cmdset_storage = f"{PATH_REMAP_PREFIX[package_name]}.{package_path}"
obj.save(update_fields=['db_cmdset_storage'])
obj.save(update_fields=["db_cmdset_storage"])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0010_auto_20210520_2137'),
("accounts", "0010_auto_20210520_2137"),
]
operations = [
migrations.RunPython(convert_contrib_typeclass_paths, migrations.RunPython.noop)
]
operations = [migrations.RunPython(convert_contrib_typeclass_paths, migrations.RunPython.noop)]

View file

@ -63,7 +63,7 @@ def build_matches(raw_string, cmdset, include_prefixes=False):
try:
orig_string = raw_string
if not include_prefixes and len(raw_string) > 1:
raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES)
raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES)
search_string = raw_string.lower()
for cmd in cmdset:
cmdname, raw_cmdname = cmd.match(search_string, include_prefixes=include_prefixes)

View file

@ -359,11 +359,15 @@ class Command(metaclass=CommandMeta):
"""
if include_prefixes:
for cmd_key in self._keyaliases:
if cmdname.startswith(cmd_key) and (not self.arg_regex or self.arg_regex.match(cmdname[len(cmd_key) :])):
if cmdname.startswith(cmd_key) and (
not self.arg_regex or self.arg_regex.match(cmdname[len(cmd_key) :])
):
return cmd_key, cmd_key
else:
for k, v in self._noprefix_aliases.items():
if cmdname.startswith(k) and (not self.arg_regex or self.arg_regex.match(cmdname[len(k) :])):
if cmdname.startswith(k) and (
not self.arg_regex or self.arg_regex.match(cmdname[len(k) :])
):
return k, v
return None, None

View file

@ -1950,7 +1950,12 @@ class CmdSetAttribute(ObjManipCommand):
if self.rhs is None:
# no = means we inspect the attribute(s)
if not attrs:
attrs = [attr.key for attr in obj.attributes.get(category=None, return_obj=True, return_list=True)]
attrs = [
attr.key
for attr in obj.attributes.get(
category=None, return_obj=True, return_list=True
)
]
for attr in attrs:
if not self.check_attr(obj, attr, category):
continue
@ -2001,7 +2006,9 @@ class CmdSetAttribute(ObjManipCommand):
result.append(self.set_attr(obj, attr, value, category))
# check if anything was done
if not result:
caller.msg("No valid attributes were found. Usage: set obj/attr[:category] = value. Use empty value to clear.")
caller.msg(
"No valid attributes were found. Usage: set obj/attr[:category] = value. Use empty value to clear."
)
else:
# send feedback
caller.msg("".join(result).strip("\n"))
@ -3732,7 +3739,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
quiet="quiet" in self.switches,
emit_to_obj=caller,
use_destination="intoexit" not in self.switches,
move_type="teleport"
move_type="teleport",
):
if obj_to_teleport == caller:

View file

@ -1584,8 +1584,9 @@ class TestBuilding(BaseEvenniaCommandTest):
self.call(
building.CmdTeleport(),
"Obj = Room2",
"Obj(#{}) is leaving Room(#{}), heading for Room2(#{}).|Teleported Obj -> Room2."
.format(oid, rid, rid2),
"Obj(#{}) is leaving Room(#{}), heading for Room2(#{}).|Teleported Obj -> Room2.".format(
oid, rid, rid2
),
)
self.call(building.CmdTeleport(), "NotFound = Room", "Could not find 'NotFound'.")
self.call(
@ -1701,8 +1702,7 @@ class TestBuilding(BaseEvenniaCommandTest):
self.call(
building.CmdSpawn(),
"{'prototype_key':'GOBLIN', 'typeclass':'evennia.objects.objects.DefaultCharacter', "
"'key':'goblin', 'location':'%s'}"
% spawnLoc.dbref,
"'key':'goblin', 'location':'%s'}" % spawnLoc.dbref,
"Spawned goblin",
)
goblin = get_object(self, "goblin")
@ -1750,8 +1750,7 @@ class TestBuilding(BaseEvenniaCommandTest):
self.call(
building.CmdSpawn(),
"/noloc {'prototype_parent':'TESTBALL', 'key': 'Ball', 'prototype_key': 'foo',"
" 'location':'%s'}"
% spawnLoc.dbref,
" 'location':'%s'}" % spawnLoc.dbref,
"Spawned Ball",
)
ball = get_object(self, "Ball")

View file

@ -13,6 +13,7 @@ def migrate_channel_aliases(apps, schema_editor):
"""
from evennia.comms.models import ChannelDB
# ChannelDB = apps.get_model("comms", "ChannelDB")
for channel in ChannelDB.objects.all():
@ -20,8 +21,12 @@ def migrate_channel_aliases(apps, schema_editor):
chan_key = channel.db_key.lower()
channel_aliases = [chan_key] + [alias.lower() for alias in channel.aliases.all()]
for subscriber in channel.subscriptions.all():
nicktuples = subscriber.nicks.get(category="channel", return_tuple=True, return_list=True)
all_aliases = channel_aliases + [tup[2] for tup in nicktuples if tup[3].lower() == chan_key]
nicktuples = subscriber.nicks.get(
category="channel", return_tuple=True, return_list=True
)
all_aliases = channel_aliases + [
tup[2] for tup in nicktuples if tup[3].lower() == chan_key
]
for key_or_alias in all_aliases:
channel.add_user_channel_alias(subscriber, key_or_alias)
except Exception as err:
@ -33,9 +38,7 @@ def migrate_channel_aliases(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('comms', '0018_auto_20191025_0831'),
("comms", "0018_auto_20191025_0831"),
]
operations = [
migrations.RunPython(migrate_channel_aliases)
]
operations = [migrations.RunPython(migrate_channel_aliases)]

View file

@ -6,21 +6,28 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comms', '0019_auto_20210514_2032'),
("comms", "0019_auto_20210514_2032"),
]
operations = [
migrations.RemoveField(
model_name='msg',
name='db_hide_from_channels',
model_name="msg",
name="db_hide_from_channels",
),
migrations.RemoveField(
model_name='msg',
name='db_receivers_channels',
model_name="msg",
name="db_receivers_channels",
),
migrations.AddField(
model_name='msg',
name='db_receiver_external',
field=models.CharField(blank=True, db_index=True, help_text='identifier for single external receiver, for use with receivers without a database existence.', max_length=1024, null=True, verbose_name='external receiver'),
model_name="msg",
name="db_receiver_external",
field=models.CharField(
blank=True,
db_index=True,
help_text="identifier for single external receiver, for use with receivers without a database existence.",
max_length=1024,
null=True,
verbose_name="external receiver",
),
),
]

View file

@ -7,51 +7,101 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0014_auto_20210520_2137'),
("scripts", "0014_auto_20210520_2137"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('objects', '0011_auto_20191025_0831'),
('comms', '0020_auto_20210514_2210'),
("objects", "0011_auto_20191025_0831"),
("comms", "0020_auto_20210514_2210"),
]
operations = [
migrations.AlterField(
model_name='msg',
name='db_receiver_external',
field=models.CharField(blank=True, db_index=True, help_text='Identifier for single external receiver, for use with recievers not represented by a regular database model.', max_length=1024, null=True, verbose_name='external receiver'),
model_name="msg",
name="db_receiver_external",
field=models.CharField(
blank=True,
db_index=True,
help_text="Identifier for single external receiver, for use with recievers not represented by a regular database model.",
max_length=1024,
null=True,
verbose_name="external receiver",
),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_accounts',
field=models.ManyToManyField(blank=True, help_text='account receivers', related_name='receiver_account_set', to=settings.AUTH_USER_MODEL, verbose_name='Receivers (Accounts)'),
model_name="msg",
name="db_receivers_accounts",
field=models.ManyToManyField(
blank=True,
help_text="account receivers",
related_name="receiver_account_set",
to=settings.AUTH_USER_MODEL,
verbose_name="Receivers (Accounts)",
),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_objects',
field=models.ManyToManyField(blank=True, help_text='object receivers', related_name='receiver_object_set', to='objects.ObjectDB', verbose_name='Receivers (Objects)'),
model_name="msg",
name="db_receivers_objects",
field=models.ManyToManyField(
blank=True,
help_text="object receivers",
related_name="receiver_object_set",
to="objects.ObjectDB",
verbose_name="Receivers (Objects)",
),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_scripts',
field=models.ManyToManyField(blank=True, help_text='script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB', verbose_name='Receivers (Scripts)'),
model_name="msg",
name="db_receivers_scripts",
field=models.ManyToManyField(
blank=True,
help_text="script_receivers",
related_name="receiver_script_set",
to="scripts.ScriptDB",
verbose_name="Receivers (Scripts)",
),
),
migrations.AlterField(
model_name='msg',
name='db_sender_accounts',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name='Senders (Accounts)'),
model_name="msg",
name="db_sender_accounts",
field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_account_set",
to=settings.AUTH_USER_MODEL,
verbose_name="Senders (Accounts)",
),
),
migrations.AlterField(
model_name='msg',
name='db_sender_external',
field=models.CharField(blank=True, db_index=True, help_text='Identifier for single external sender, for use with senders not represented by a regular database model.', max_length=255, null=True, verbose_name='external sender'),
model_name="msg",
name="db_sender_external",
field=models.CharField(
blank=True,
db_index=True,
help_text="Identifier for single external sender, for use with senders not represented by a regular database model.",
max_length=255,
null=True,
verbose_name="external sender",
),
),
migrations.AlterField(
model_name='msg',
name='db_sender_objects',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_object_set', to='objects.ObjectDB', verbose_name='Senders (Objects)'),
model_name="msg",
name="db_sender_objects",
field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_object_set",
to="objects.ObjectDB",
verbose_name="Senders (Objects)",
),
),
migrations.AlterField(
model_name='msg',
name='db_sender_scripts',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name='Senders (Scripts)'),
model_name="msg",
name="db_sender_scripts",
field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_script_set",
to="scripts.ScriptDB",
verbose_name="Senders (Scripts)",
),
),
]

View file

@ -234,7 +234,9 @@ class EventCharacter(DefaultCharacter):
if not string:
return
super().announce_move_from(destination, msg=string, move_type=move_type, mapping=mapping, **kwargs)
super().announce_move_from(
destination, msg=string, move_type=move_type, mapping=mapping, **kwargs
)
def announce_move_to(self, source_location, msg=None, move_type="move", mapping=None, **kwargs):
"""
@ -292,7 +294,9 @@ class EventCharacter(DefaultCharacter):
if not string:
return
super().announce_move_to(source_location, msg=string, move_type=move_type, mapping=mapping, **kwargs)
super().announce_move_to(
source_location, msg=string, move_type=move_type, mapping=mapping, **kwargs
)
def at_pre_move(self, destination, move_type="move", **kwargs):
"""

View file

@ -236,7 +236,9 @@ class CmdGiveUp(CmdEvscapeRoom):
# manually call move hooks
self.room.msg_room(self.caller, f"|r{self.caller.key} gave up and was whisked away!|n")
self.room.at_object_leave(self.caller, self.caller.home)
self.caller.move_to(self.caller.home, quiet=True, move_hooks=False, move_type="teleport")
self.caller.move_to(
self.caller.home, quiet=True, move_hooks=False, move_type="teleport"
)
# back to menu
run_evscaperoom_menu(self.caller)

View file

@ -236,6 +236,7 @@ class RecogError(Exception):
class LanguageError(Exception):
pass
def _get_case_ref(string):
"""
Helper function which parses capitalization and
@ -257,6 +258,7 @@ def _get_case_ref(string):
return case
# emoting mechanisms
def parse_language(speaker, emote):
"""
@ -405,7 +407,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
match_index = marker_match.start()
# split the emote string at the reference marker, to process everything after it
head = string[:match_index]
tail = string[match_index + 1:]
tail = string[match_index + 1 :]
if search_mode:
# match the candidates against the whole search string after the marker
@ -451,7 +453,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
# save search string
matched_text = "".join(tail[1:iend])
# recombine remainder of emote back into a string
tail = "".join(tail[iend + 1:])
tail = "".join(tail[iend + 1 :])
nmatches = len(bestmatches)
@ -1275,19 +1277,19 @@ class ContribRPObject(DefaultObject):
self.sdesc.add("Something")
def search(
self,
searchdata,
global_search=False,
use_nicks=True,
typeclass=None,
location=None,
attribute_name=None,
quiet=False,
exact=False,
candidates=None,
nofound_string=None,
multimatch_string=None,
use_dbref=None,
self,
searchdata,
global_search=False,
use_nicks=True,
typeclass=None,
location=None,
attribute_name=None,
quiet=False,
exact=False,
candidates=None,
nofound_string=None,
multimatch_string=None,
use_dbref=None,
):
"""
Returns an Object matching a search string/condition, taking
@ -1371,10 +1373,10 @@ class ContribRPObject(DefaultObject):
)
if global_search or (
is_string
and searchdata.startswith("#")
and len(searchdata) > 1
and searchdata[1:].isdigit()
is_string
and searchdata.startswith("#")
and len(searchdata) > 1
and searchdata[1:].isdigit()
):
# only allow exact matching if searching the entire database
# or unique #dbrefs

View file

@ -151,7 +151,13 @@ class TestRPSystem(BaseEvenniaTest):
id2 = f"#{self.receiver2.id}"
candidates = (self.receiver1, self.receiver2)
result = (
'With a flair, {'+id0+'} looks at {'+id1+'} and {'+id2+'}. She says "This is a test."',
"With a flair, {"
+ id0
+ "} looks at {"
+ id1
+ "} and {"
+ id2
+ '}. She says "This is a test."',
{
id2: self.receiver2,
id1: self.receiver1,
@ -178,7 +184,7 @@ class TestRPSystem(BaseEvenniaTest):
id2 = f"#{self.receiver2.id}"
candidates = (self.receiver1, self.receiver2)
result = (
"{"+id0+"} frowns at {"+id1+"} for trying to steal {"+id0+"}'s test.",
"{" + id0 + "} frowns at {" + id1 + "} for trying to steal {" + id0 + "}'s test.",
{
id1: self.receiver1,
id0: speaker,

View file

@ -332,11 +332,9 @@ class EvAdventureRollEngine:
setattr(character, abi, current_abi)
character.msg(
"~" * 78
+ "\n|yYou survive your brush with death, "
"~" * 78 + "\n|yYou survive your brush with death, "
f"but are |r{result.upper()}|y and permanently |rlose {loss} {abi}|y.|n\n"
f"|GYou recover |g{new_hp}|G health|.\n"
+ "~" * 78
f"|GYou recover |g{new_hp}|G health|.\n" + "~" * 78
)

View file

@ -76,48 +76,121 @@ from evennia.utils.utils import is_iter
# Load name data from Behind the Name lists
dirpath = path.dirname(path.abspath(__file__))
_FIRSTNAME_LIST = []
with open(path.join(dirpath, "btn_givennames.txt"),'r', encoding='utf-8') as file:
_FIRSTNAME_LIST = [ line.strip().rsplit(" ") for line in file if line and not line.startswith("#") ]
with open(path.join(dirpath, "btn_givennames.txt"), "r", encoding="utf-8") as file:
_FIRSTNAME_LIST = [
line.strip().rsplit(" ") for line in file if line and not line.startswith("#")
]
_SURNAME_LIST = []
with open(path.join(dirpath, "btn_surnames.txt"),'r', encoding='utf-8') as file:
_SURNAME_LIST = [ line.strip() for line in file if line and not line.startswith("#") ]
with open(path.join(dirpath, "btn_surnames.txt"), "r", encoding="utf-8") as file:
_SURNAME_LIST = [line.strip() for line in file if line and not line.startswith("#")]
_REQUIRED_KEYS = { "syllable", "consonants", "vowels", "length" }
_REQUIRED_KEYS = {"syllable", "consonants", "vowels", "length"}
# Define phoneme structure for built-in fantasy name generators.
_FANTASY_NAME_STRUCTURES = {
"harsh": {
"syllable": "CV(C)",
"consonants": [ "k", "k", "k", "z", "zh", "g", "v", "t", "th", "w", "n", "d", "d", ],
"start": ["dh", "kh", "kh", "kh", "vh", ],
"end": ["n", "x", ],
"vowels": [ "o", "o", "o", "a", "y", "u", "u", "u", "ä", "ö", "e", "i", "i", ],
"length": (1,3),
"syllable": "CV(C)",
"consonants": [
"k",
"k",
"k",
"z",
"zh",
"g",
"v",
"t",
"th",
"w",
"n",
"d",
"d",
],
"start": [
"dh",
"kh",
"kh",
"kh",
"vh",
],
"end": [
"n",
"x",
],
"vowels": [
"o",
"o",
"o",
"a",
"y",
"u",
"u",
"u",
"ä",
"ö",
"e",
"i",
"i",
],
"length": (1, 3),
},
"fluid": {
"syllable": "V(C)",
"consonants": [ 'r','r','l','l','l','l','s','s','s','sh','m','n','n','f','v','w','th' ],
"start": [],
"end": [],
"vowels": [ "a","a","a","a","a","e","i","i","i","y","u","o", ],
"length": (3,5),
"syllable": "V(C)",
"consonants": [
"r",
"r",
"l",
"l",
"l",
"l",
"s",
"s",
"s",
"sh",
"m",
"n",
"n",
"f",
"v",
"w",
"th",
],
"start": [],
"end": [],
"vowels": [
"a",
"a",
"a",
"a",
"a",
"e",
"i",
"i",
"i",
"y",
"u",
"o",
],
"length": (3, 5),
},
"alien": {
"syllable": "C(C(V))(')(C)",
"consonants": [ 'q','q','x','z','v','w','k','h','b' ],
"start": ['x',],
"end": [],
"vowels": [ 'y','w','o','y' ],
"length": (1,5),
"syllable": "C(C(V))(')(C)",
"consonants": ["q", "q", "x", "z", "v", "w", "k", "h", "b"],
"start": [
"x",
],
"end": [],
"vowels": ["y", "w", "o", "y"],
"length": (1, 5),
},
}
}
_RE_DOUBLES = re.compile(r'(\w)\1{2,}')
_RE_DOUBLES = re.compile(r"(\w)\1{2,}")
# Load in optional settings
custom_first_names = settings.NAMEGEN_FIRST_NAMES if hasattr(settings, "NAMEGEN_FIRST_NAMES") else []
custom_first_names = (
settings.NAMEGEN_FIRST_NAMES if hasattr(settings, "NAMEGEN_FIRST_NAMES") else []
)
custom_last_names = settings.NAMEGEN_LAST_NAMES if hasattr(settings, "NAMEGEN_LAST_NAMES") else []
if hasattr(settings, "NAMEGEN_FANTASY_RULES"):
@ -132,7 +205,6 @@ else:
_SURNAME_LIST += custom_last_names
def fantasy_name(num=1, style="harsh", return_list=False):
"""
Generate made-up names in one of a number of "styles".
@ -143,28 +215,34 @@ def fantasy_name(num=1, style="harsh", return_list=False):
return_list (bool) - Whether to always return a list. `False` by default,
which returns a string if there is only one value and a list if more.
"""
def _validate(style_name):
if style_name not in _FANTASY_NAME_STRUCTURES:
raise ValueError(f"Invalid style name: '{style_name}'. Available style names: {' '.join(_FANTASY_NAME_STRUCTURES.keys())}")
raise ValueError(
f"Invalid style name: '{style_name}'. Available style names: {' '.join(_FANTASY_NAME_STRUCTURES.keys())}"
)
style_dict = _FANTASY_NAME_STRUCTURES[style_name]
if type(style_dict) is not dict:
raise ValueError(f"Style {style_name} must be a dictionary.")
keys = set(style_dict.keys())
missing_keys = _REQUIRED_KEYS - keys
if len(missing_keys):
raise KeyError(f"Style dictionary {style_name} is missing required keys: {' '.join(missing_keys)}")
if not (type(style_dict['consonants']) is list and type(style_dict['vowels']) is list):
raise KeyError(
f"Style dictionary {style_name} is missing required keys: {' '.join(missing_keys)}"
)
if not (type(style_dict["consonants"]) is list and type(style_dict["vowels"]) is list):
raise TypeError(f"'consonants' and 'vowels' for style {style_name} must be lists.")
if not (is_iter(style_dict['length']) and len(style_dict['length']) == 2):
raise ValueError(f"'length' key for {style_name} must have a minimum and maximum number of syllables.")
if not (is_iter(style_dict["length"]) and len(style_dict["length"]) == 2):
raise ValueError(
f"'length' key for {style_name} must have a minimum and maximum number of syllables."
)
return style_dict
# validate num first
num = int(num)
if num < 1:
@ -178,9 +256,9 @@ def fantasy_name(num=1, style="harsh", return_list=False):
for key in style_dict["syllable"]:
# parentheses mean optional - allow nested parens
if key == "(":
weight = weight/2
weight = weight / 2
elif key == ")":
weight = weight*2
weight = weight * 2
else:
if key == "C":
sound_type = "consonants"
@ -189,23 +267,23 @@ def fantasy_name(num=1, style="harsh", return_list=False):
else:
sound_type = key
# append the sound type and weight
syllable.append( (sound_type, int(weight)) )
syllable.append((sound_type, int(weight)))
name_list = []
# time to generate a name!
for n in range(num):
# build a list of syllables
length = random.randint(*style_dict['length'])
length = random.randint(*style_dict["length"])
name = ""
for i in range(length):
# build the syllable itself
syll = ""
for sound, weight in syllable:
# random chance to skip this key; lower weights mean less likely
if random.randint(0,8) > weight:
if random.randint(0, 8) > weight:
continue
if sound not in style_dict:
# extra character, like apostrophes
syll += sound
@ -217,33 +295,38 @@ def fantasy_name(num=1, style="harsh", return_list=False):
if sound == "consonants":
# if it's a starting consonant, add starting-sounds to the options
if not len(syll):
choices += style_dict.get('start',[])
choices += style_dict.get("start", [])
# if it's an ending consonant, add ending-sounds to the options
elif i+1 == length:
choices += style_dict.get('end',[])
elif i + 1 == length:
choices += style_dict.get("end", [])
syll += random.choice(choices)
name += syll
# condense repeating letters down to a maximum of 2
name = _RE_DOUBLES.sub(lambda m: m.group(1)*2, name)
name = _RE_DOUBLES.sub(lambda m: m.group(1) * 2, name)
# capitalize the first letter
name = name[0].upper() + name[1:] if len(name) > 1 else name.upper()
name_list.append(name)
if len(name_list) == 1 and not return_list:
return name_list[0]
return name_list
def first_name(num=1, gender=None, return_list=False, ):
def first_name(
num=1,
gender=None,
return_list=False,
):
"""
Generate first names, also known as personal names.
Keyword args:
num (int) - How many names to return.
gender (str) - Restrict names by gender association. `None` by default, which selects from
all possible names. Set to "m" for masculine, "f" for feminine, "mf" for androgynous
all possible names. Set to "m" for masculine, "f" for feminine, "mf" for androgynous
return_list (bool) - Whether to always return a list. `False` by default,
which returns a string if there is only one value and a list if more.
"""
@ -251,29 +334,33 @@ def first_name(num=1, gender=None, return_list=False, ):
num = int(num)
if num < 1:
raise ValueError("Number of names to generate must be positive.")
if gender:
# filter the options by gender
name_options = [ name_data[0] for name_data in _FIRSTNAME_LIST if all([gender_key in gender for gender_key in name_data[1]])]
name_options = [
name_data[0]
for name_data in _FIRSTNAME_LIST
if all([gender_key in gender for gender_key in name_data[1]])
]
if not len(name_options):
raise ValueError(f"Invalid gender '{gender}'.")
else:
name_options = [ name_data[0] for name_data in _FIRSTNAME_LIST ]
name_options = [name_data[0] for name_data in _FIRSTNAME_LIST]
# take a random selection of `num` names, without repeats
results = random.sample(name_options,num)
results = random.sample(name_options, num)
if len(results) == 1 and not return_list:
# return single value as a string
return results[0]
return results
def last_name(num=1, return_list=False):
"""
Generate family names, also known as surnames or last names.
Keyword args:
num (int) - How many names to return.
return_list (bool) - Whether to always return a list. `False` by default,
@ -285,7 +372,7 @@ def last_name(num=1, return_list=False):
raise ValueError("Number of names to generate must be positive.")
# take a random selection of `num` names, without repeats
results = random.sample(_SURNAME_LIST,num)
results = random.sample(_SURNAME_LIST, num)
if len(results) == 1 and not return_list:
# return single value as a string
@ -293,15 +380,16 @@ def last_name(num=1, return_list=False):
return results
def full_name(num=1, parts=2, gender=None, return_list=False, surname_first=False):
"""
Generate complete names with a personal name, family name, and optionally middle names.
Keyword args:
num (int) - How many names to return.
parts (int) - How many parts the name should have. By default two: first and last.
gender (str) - Restrict names by gender association. `None` by default, which selects from
all possible names. Set to "m" for masculine, "f" for feminine, "mf" for androgynous
all possible names. Set to "m" for masculine, "f" for feminine, "mf" for androgynous
return_list (bool) - Whether to always return a list. `False` by default,
which returns a string if there is only one value and a list if more.
surname_first (bool) - Default `False`. Set to `True` if you want the family name to be
@ -318,26 +406,26 @@ def full_name(num=1, parts=2, gender=None, return_list=False, surname_first=Fals
name_lists = []
middle = parts-2
middle = parts - 2
if middle:
# calculate "middle" names.
# we want them to be an intelligent mix of personal names and family names
# first, split the total number of middle-name parts into "personal" and "family" at a random point
total_mids = middle*num
personals = random.randint(1,total_mids)
total_mids = middle * num
personals = random.randint(1, total_mids)
familys = total_mids - personals
# then get the names for each
personal_mids = first_name(num=personals, gender=gender, return_list=True)
family_mids = last_name(num=familys, return_list=True) if familys else []
# splice them together according to surname_first....
middle_names = family_mids+personal_mids if surname_first else personal_mids+family_mids
middle_names = family_mids + personal_mids if surname_first else personal_mids + family_mids
# ...and then split into `num`-length lists to be used for the final names
name_lists = [ middle_names[num*i:num*(i+1)] for i in range(0,middle) ]
name_lists = [middle_names[num * i : num * (i + 1)] for i in range(0, middle)]
# get personal and family names
personal_names = first_name(num=num, gender=gender, return_list=True)
last_names = last_name(num=num, return_list=True)
# attach personal/family names to the list of name lists, according to surname_first
if surname_first:
name_lists = [last_names] + name_lists + [personal_names]
@ -346,7 +434,7 @@ def full_name(num=1, parts=2, gender=None, return_list=False, surname_first=Fals
# lastly, zip them all up and join them together
names = list(zip(*name_lists))
names = [ " ".join(name) for name in names ]
names = [" ".join(name) for name in names]
if len(names) == 1 and not return_list:
# return single value as a string

View file

@ -1,4 +1,3 @@
"""
Tests for the Random Name Generator
"""
@ -8,35 +7,36 @@ from evennia.contrib.utils.name_generator import namegen
_INVALID_STYLES = {
"missing_keys": {
"consonants": ['c','d'],
"length": (1,2),
"consonants": ["c", "d"],
"length": (1, 2),
},
"invalid_vowels": {
"syllable": "CVC",
"consonants": ['c','d'],
"consonants": ["c", "d"],
"vowels": "aeiou",
"length": (1,2),
"length": (1, 2),
},
"invalid_length": {
"syllable": "CVC",
"consonants": ['c','d'],
"vowels": ['a','e'],
"consonants": ["c", "d"],
"vowels": ["a", "e"],
"length": 2,
},
}
namegen._FANTASY_NAME_STRUCTURES |= _INVALID_STYLES
class TestNameGenerator(BaseEvenniaTest):
def test_fantasy_name(self):
"""
Verify output types and lengths.
fantasy_name() - str
fantasy_name(style="fluid") - str
fantasy_name(num=3) - list of length 3
fantasy_name(return_list=True) - list of length 1
raises KeyError on missing style or ValueError on num
"""
single_name = namegen.fantasy_name()
@ -55,27 +55,27 @@ class TestNameGenerator(BaseEvenniaTest):
with self.assertRaises(ValueError):
namegen.fantasy_name(num=-1)
with self.assertRaises(ValueError):
namegen.fantasy_name(style="dummy")
def test_structure_validation(self):
"""
Verify that validation raises the correct errors for invalid inputs.
"""
with self.assertRaises(KeyError):
namegen.fantasy_name(style="missing_keys")
namegen.fantasy_name(style="missing_keys")
with self.assertRaises(TypeError):
namegen.fantasy_name(style="invalid_vowels")
namegen.fantasy_name(style="invalid_vowels")
with self.assertRaises(ValueError):
namegen.fantasy_name(style="invalid_length")
namegen.fantasy_name(style="invalid_length")
def test_first_name(self):
"""
Verify output types and lengths.
first_name() - str
first_name(num=3) - list of length 3
first_name(gender='f') - str
@ -88,7 +88,7 @@ class TestNameGenerator(BaseEvenniaTest):
self.assertEqual(type(three_names), list)
self.assertEqual(len(three_names), 3)
gendered_name = namegen.first_name(gender='f')
gendered_name = namegen.first_name(gender="f")
self.assertEqual(type(gendered_name), str)
single_list = namegen.first_name(return_list=True)
@ -96,7 +96,7 @@ class TestNameGenerator(BaseEvenniaTest):
self.assertEqual(len(single_list), 1)
with self.assertRaises(ValueError):
namegen.first_name(gender='x')
namegen.first_name(gender="x")
with self.assertRaises(ValueError):
namegen.first_name(num=-1)
@ -104,7 +104,7 @@ class TestNameGenerator(BaseEvenniaTest):
def test_last_name(self):
"""
Verify output types and lengths.
last_name() - str
last_name(num=3) - list of length 3
last_name(return_list=True) - list of length 1
@ -126,7 +126,7 @@ class TestNameGenerator(BaseEvenniaTest):
def test_full_name(self):
"""
Verify output types and lengths.
full_name() - str
full_name(num=3) - list of length 3
full_name(gender='f') - str
@ -139,7 +139,7 @@ class TestNameGenerator(BaseEvenniaTest):
self.assertEqual(type(three_names), list)
self.assertEqual(len(three_names), 3)
gendered_name = namegen.full_name(gender='f')
gendered_name = namegen.full_name(gender="f")
self.assertEqual(type(gendered_name), str)
single_list = namegen.full_name(return_list=True)

View file

@ -17,12 +17,14 @@ at_server_cold_stop()
"""
def at_server_init():
"""
This is called first as the server is starting up, regardless of how.
"""
pass
def at_server_start():
"""
This is called every time the server starts up, regardless of

View file

@ -6,17 +6,17 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('help', '0003_auto_20190128_1820'),
("help", "0003_auto_20190128_1820"),
]
operations = [
migrations.RemoveField(
model_name='helpentry',
name='db_staff_only',
model_name="helpentry",
name="db_staff_only",
),
migrations.AddField(
model_name='helpentry',
name='db_date_created',
field=models.DateTimeField(auto_now=True, verbose_name='creation date'),
model_name="helpentry",
name="db_date_created",
field=models.DateTimeField(auto_now=True, verbose_name="creation date"),
),
]

View file

@ -2,6 +2,7 @@
from django.db import migrations
def update_help_entries(apps, schema_editor):
"""
Change all help-entry files that use view: locks to read: locks
@ -11,8 +12,10 @@ def update_help_entries(apps, schema_editor):
HelpEntry = apps.get_model("help", "HelpEntry")
for help_entry in HelpEntry.objects.all():
lock_storage = help_entry.db_lock_storage
lock_storage = dict(lstring.split(":", 1) if ":" in lstring else (lstring, "")
for lstring in str(lock_storage).split(";"))
lock_storage = dict(
lstring.split(":", 1) if ":" in lstring else (lstring, "")
for lstring in str(lock_storage).split(";")
)
if "read" in lock_storage:
# already in place - skip
continue
@ -26,9 +29,7 @@ def update_help_entries(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('help', '0004_auto_20210520_2137'),
("help", "0004_auto_20210520_2137"),
]
operations = [
migrations.RunPython(update_help_entries)
]
operations = [migrations.RunPython(update_help_entries)]

View file

@ -41,7 +41,7 @@ PATH_REMAP_PREFIX = {
"auditing": "evennia.contrib.utils",
"fieldfill": "evennia.contrib.utils",
"random_string_generator": "evennia.contrib.utils",
"tree_select": "evennia.contrib.utils"
"tree_select": "evennia.contrib.utils",
}
@ -52,43 +52,45 @@ def convert_contrib_typeclass_paths(apps, schema_editor):
try:
package_path = obj.db_typeclass_path.split(".")[2:]
package_name = package_path[0]
if package_path[0] == 'security':
if package_path[0] == "security":
# renamed package and changed path
package_name = 'auditing'
package_name = "auditing"
package_path.pop(0) # no longer security/auditing
if package_path[-1] == ".Clothing":
# renamed Clothing class to ContribClothing
package_path[-1] = "ContribClothing"
package_path = '.'.join(package_path)
package_path = ".".join(package_path)
except IndexError:
print(f"obj.db_typeclass_path={obj.db_typeclass_path} could not be parsed "
"for converting to the new contrib location.")
print(
f"obj.db_typeclass_path={obj.db_typeclass_path} could not be parsed "
"for converting to the new contrib location."
)
continue
if package_name in PATH_REMAP_PREFIX:
obj.db_typeclass_path = f"{PATH_REMAP_PREFIX[package_name]}.{package_path}"
obj.save(update_fields=['db_typeclass_path'])
obj.save(update_fields=["db_typeclass_path"])
for obj in ObjectDB.objects.filter(db_cmdset_storage__startswith="evennia.contrib."):
try:
package_path = obj.db_cmdset_storage.split(".")[2:]
package_name = package_path[0]
package_path = '.'.join(package_path)
package_path = ".".join(package_path)
except IndexError:
print(f"obj.db_cmdset_storage={obj.db_cmdset_storage} could not be parsed "
"for converting to the new contrib location.")
print(
f"obj.db_cmdset_storage={obj.db_cmdset_storage} could not be parsed "
"for converting to the new contrib location."
)
continue
if package_name in PATH_REMAP_PREFIX:
obj.db_cmdset_storage = f"{PATH_REMAP_PREFIX[package_name]}.{package_path}"
obj.save(update_fields=['db_cmdset_storage'])
obj.save(update_fields=["db_cmdset_storage"])
class Migration(migrations.Migration):
dependencies = [
('objects', '0011_auto_20191025_0831'),
("objects", "0011_auto_20191025_0831"),
]
operations = [
migrations.RunPython(convert_contrib_typeclass_paths, migrations.RunPython.noop)
]
operations = [migrations.RunPython(convert_contrib_typeclass_paths, migrations.RunPython.noop)]

View file

@ -2,8 +2,7 @@ from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
from evennia.objects.models import ObjectDB
from evennia.objects.objects import DefaultObject
from evennia.typeclasses.attributes import AttributeProperty
from evennia.typeclasses.tags import (AliasProperty, PermissionProperty,
TagProperty)
from evennia.typeclasses.tags import AliasProperty, PermissionProperty, TagProperty
from evennia.utils import create
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTestCase
@ -240,14 +239,16 @@ class SubAttributeProperty(AttributeProperty):
class SubTagProperty(TagProperty):
pass
class CustomizedProperty(AttributeProperty):
def at_set(self, value, obj):
obj.settest = value
return value
def at_get(self, value, obj):
return value + obj.awaretest
class TestObjectPropertiesClass(DefaultObject):
attr1 = AttributeProperty(default="attr1")
attr2 = AttributeProperty(default="attr2", category="attrcategory")
@ -266,6 +267,7 @@ class TestObjectPropertiesClass(DefaultObject):
def base_property(self):
self.property_initialized = True
class TestProperties(EvenniaTestCase):
"""
Test Properties.
@ -273,7 +275,9 @@ class TestProperties(EvenniaTestCase):
"""
def setUp(self):
self.obj: TestObjectPropertiesClass = create.create_object(TestObjectPropertiesClass, key="testobj")
self.obj: TestObjectPropertiesClass = create.create_object(
TestObjectPropertiesClass, key="testobj"
)
def tearDown(self):
self.obj.delete()
@ -317,7 +321,7 @@ class TestProperties(EvenniaTestCase):
self.assertFalse(hasattr(obj, "property_initialized"))
def test_object_awareness(self):
'''Test the "object-awareness" of customized AttributeProperty getter/setters'''
"""Test the "object-awareness" of customized AttributeProperty getter/setters"""
obj = self.obj
# attribute properties receive on obj ref in the getter/setter that can customize return

View file

@ -6,18 +6,22 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0013_auto_20191025_0831'),
("scripts", "0013_auto_20191025_0831"),
]
operations = [
migrations.AlterField(
model_name='scriptdb',
name='db_interval',
field=models.IntegerField(default=-1, help_text='how often to repeat script, in seconds. <= 0 means off.', verbose_name='interval'),
model_name="scriptdb",
name="db_interval",
field=models.IntegerField(
default=-1,
help_text="how often to repeat script, in seconds. <= 0 means off.",
verbose_name="interval",
),
),
migrations.AlterField(
model_name='scriptdb',
name='db_persistent',
field=models.BooleanField(default=True, verbose_name='survive server reboot'),
model_name="scriptdb",
name="db_persistent",
field=models.BooleanField(default=True, verbose_name="survive server reboot"),
),
]

View file

@ -41,7 +41,7 @@ PATH_REMAP_PREFIX = {
"auditing": "evennia.contrib.utils",
"fieldfill": "evennia.contrib.utils",
"random_string_generator": "evennia.contrib.utils",
"tree_select": "evennia.contrib.utils"
"tree_select": "evennia.contrib.utils",
}
@ -52,30 +52,30 @@ def convert_contrib_typeclass_paths(apps, schema_editor):
try:
package_path = obj.db_typeclass_path.split(".")[2:]
package_name = package_path[0]
if package_path[0] == 'security':
if package_path[0] == "security":
# renamed package and changed path
package_name = 'auditing'
package_name = "auditing"
package_path.pop(0) # no longer security/auditing
if package_path[-1] == ".Clothing":
# renamed Clothing class to ContribClothing
package_path[-1] = "ContribClothing"
package_path = '.'.join(package_path)
package_path = ".".join(package_path)
except IndexError:
print(f"obj.db_typeclass_path={obj.db_typeclass_path} could not be parsed "
"for converting to the new contrib location.")
print(
f"obj.db_typeclass_path={obj.db_typeclass_path} could not be parsed "
"for converting to the new contrib location."
)
continue
if package_name in PATH_REMAP_PREFIX:
obj.db_typeclass_path = f"{PATH_REMAP_PREFIX[package_name]}.{package_path}"
obj.save(update_fields=['db_typeclass_path'])
obj.save(update_fields=["db_typeclass_path"])
class Migration(migrations.Migration):
dependencies = [
('scripts', '0014_auto_20210520_2137'),
("scripts", "0014_auto_20210520_2137"),
]
operations = [
migrations.RunPython(convert_contrib_typeclass_paths, migrations.RunPython.noop)
]
operations = [migrations.RunPython(convert_contrib_typeclass_paths, migrations.RunPython.noop)]

View file

@ -47,8 +47,11 @@ _SA = object.__setattr__
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server", "server.restart")
# modules containing hook methods called during start_stop
SERVER_STARTSTOP_MODULES = [mod_import(mod) for mod in make_iter(settings.AT_SERVER_STARTSTOP_MODULE)
if isinstance(mod, str)]
SERVER_STARTSTOP_MODULES = [
mod_import(mod)
for mod in make_iter(settings.AT_SERVER_STARTSTOP_MODULE)
if isinstance(mod, str)
]
# modules containing plugin services
SERVER_SERVICES_PLUGIN_MODULES = make_iter(settings.SERVER_SERVICES_PLUGIN_MODULES)

View file

@ -202,18 +202,18 @@ class AttributeProperty:
self._lockstring = lockstring
self._autocreate = autocreate
self._key = ""
@property
def _default(self):
"""
Tries returning a new instance of default if callable.
"""
if callable(self.__default):
return self.__default()
return self.__default
@_default.setter
def _default(self, value):
self.__default = value
@ -233,13 +233,16 @@ class AttributeProperty:
"""
value = self._default
try:
value = self.at_get(getattr(instance, self.attrhandler_name).get(
key=self._key,
default=self._default,
category=self._category,
strattr=self._strattr,
raise_exception=self._autocreate,
), instance)
value = self.at_get(
getattr(instance, self.attrhandler_name).get(
key=self._key,
default=self._default,
category=self._category,
strattr=self._strattr,
raise_exception=self._autocreate,
),
instance,
)
except AttributeError:
if self._autocreate:
# attribute didn't exist and autocreate is set

View file

@ -6,13 +6,20 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('typeclasses', '0013_auto_20191015_1922'),
("typeclasses", "0013_auto_20191015_1922"),
]
operations = [
migrations.AlterField(
model_name='tag',
name='db_category',
field=models.CharField(blank=True, db_index=True, help_text='tag category', max_length=64, null=True, verbose_name='category'),
model_name="tag",
name="db_category",
field=models.CharField(
blank=True,
db_index=True,
help_text="tag category",
max_length=64,
null=True,
verbose_name="category",
),
),
]

View file

@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('typeclasses', '0014_alter_tag_db_category'),
("typeclasses", "0014_alter_tag_db_category"),
]
operations = [
migrations.AlterModelOptions(
name='attribute',
options={'verbose_name': 'Attribute'},
name="attribute",
options={"verbose_name": "Attribute"},
),
]

View file

@ -2,8 +2,8 @@ from evennia.scripts.scripts import DefaultScript
from evennia.utils.test_resources import EvenniaTest
from evennia.utils.search import search_script_attribute, search_script_tag, search_script
class TestSearch(EvenniaTest):
class TestSearch(EvenniaTest):
def test_search_script_tag(self):
"""Check that a script can be found by its tag."""
script, errors = DefaultScript.create("a-script")
@ -19,21 +19,21 @@ class TestSearch(EvenniaTest):
found = search_script_tag("a-tag", category="a-category")
self.assertEqual(len(found), 1, errors)
self.assertEqual(script.key, found[0].key, errors)
def test_search_script_tag_wrong_category(self):
"""Check that a script cannot be found by the wrong category."""
script, errors = DefaultScript.create("a-script")
script.tags.add("a-tag", category="a-category")
found = search_script_tag("a-tag", category="wrong-category")
self.assertEqual(len(found), 0, errors)
def test_search_script_tag_wrong(self):
"""Check that a script cannot be found by the wrong tag."""
script, errors = DefaultScript.create("a-script")
script.tags.add("a-tag", category="a-category")
found = search_script_tag("wrong-tag", category="a-category")
self.assertEqual(len(found), 0, errors)
def test_search_script_attribute(self):
"""Check that a script can be found by its attributes."""
script, errors = DefaultScript.create("a-script")
@ -41,24 +41,23 @@ class TestSearch(EvenniaTest):
found = search_script_attribute(key="an_attribute", value="some value")
self.assertEqual(len(found), 1, errors)
self.assertEqual(script.key, found[0].key, errors)
def test_search_script_attribute_wrong(self):
"""Check that a script cannot be found by wrong value of its attributes."""
script, errors = DefaultScript.create("a-script")
script.db.an_attribute = "some value"
found = search_script_attribute(key="an_attribute", value="wrong value")
self.assertEqual(len(found), 0, errors)
def test_search_script_key(self):
"""Check that a script can be found by its key value."""
script, errors = DefaultScript.create("a-script")
found = search_script("a-script")
self.assertEqual(len(found), 1, errors)
self.assertEqual(script.key, found[0].key, errors)
def test_search_script_wrong_key(self):
"""Check that a script cannot be found by a wrong key value."""
script, errors = DefaultScript.create("a-script")
found = search_script("wrong_key")
self.assertEqual(len(found), 0, errors)

View file

@ -80,7 +80,9 @@ class TestListToString(TestCase):
self.assertEqual("1, 2 and 3", utils.list_to_string([1, 2, 3], endsep="and"))
self.assertEqual("1, 2 3", utils.list_to_string([1, 2, 3], endsep=""))
self.assertEqual("1; 2; 3", utils.list_to_string([1, 2, 3], sep=";", endsep=";"))
self.assertEqual('"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep=",", addquote=True))
self.assertEqual(
'"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep=",", addquote=True)
)
self.assertEqual(
'"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True)
)

View file

@ -275,8 +275,7 @@ class CharacterPuppetView(EvenniaWebTest):
self.assertTrue(
response.status_code >= 400,
"Invalid access should return a 4xx code-- either obj not found or permission denied!"
" (Returned %s)"
% response.status_code,
" (Returned %s)" % response.status_code,
)