Ran black on sources

This commit is contained in:
Griatch 2020-04-12 12:19:15 +02:00
parent cc5aa91be1
commit 21d62e651a
19 changed files with 362 additions and 322 deletions

View file

@ -90,7 +90,7 @@ def _init_command(cls, **kwargs):
"aliases": " ".join(cls.aliases), "aliases": " ".join(cls.aliases),
"category": cls.help_category, "category": cls.help_category,
"text": cls.__doc__, "text": cls.__doc__,
"tags": "" "tags": "",
} }

View file

@ -34,13 +34,7 @@ class HelpCategory:
@property @property
def search_index_entry(self): def search_index_entry(self):
return { return {"key": str(self), "aliases": "", "category": self.key, "tags": "", "text": ""}
"key": str(self),
"aliases": "",
"category": self.key,
"tags": "",
"text": ""
}
def __str__(self): def __str__(self):
return f"Category: {self.key}" return f"Category: {self.key}"
@ -59,28 +53,13 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5):
search_index = lunr( search_index = lunr(
ref="key", ref="key",
fields=[ fields=[
{ {"field_name": "key", "boost": 10,},
"field_name": "key", {"field_name": "aliases", "boost": 9,},
"boost": 10, {"field_name": "category", "boost": 8,},
}, {"field_name": "tags", "boost": 5},
{ {"field_name": "text", "boost": 1,},
"field_name": "aliases",
"boost": 9,
},
{
"field_name": "category",
"boost": 8,
},
{
"field_name": "tags",
"boost": 5
},
{
"field_name": "text",
"boost": 1,
},
], ],
documents=indx documents=indx,
) )
try: try:
matches = search_index.search(query)[:suggestion_maxnum] matches = search_index.search(query)[:suggestion_maxnum]
@ -89,9 +68,10 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5):
matches = [] matches = []
# matches (objs), suggestions (strs) # matches (objs), suggestions (strs)
return ([mapping[match["ref"]] for match in matches], return (
[str(match["ref"]) # + f" (score {match['score']})") # good debug [mapping[match["ref"]] for match in matches],
for match in matches]) [str(match["ref"]) for match in matches], # + f" (score {match['score']})") # good debug
)
class CmdHelp(Command): class CmdHelp(Command):
@ -195,11 +175,11 @@ class CmdHelp(Command):
for category in sorted(set(list(hdict_cmds.keys()) + list(hdict_db.keys()))): for category in sorted(set(list(hdict_cmds.keys()) + list(hdict_db.keys()))):
category_str = f"-- {category.title()} " category_str = f"-- {category.title()} "
grid.append(ANSIString( grid.append(
category_clr ANSIString(
+ category_str category_clr + category_str + "-" * (width - len(category_str)) + topic_clr
+ "-" * (width - len(category_str)) )
+ topic_clr)) )
verbatim_elements.append(len(grid) - 1) verbatim_elements.append(len(grid) - 1)
entries = sorted(set(hdict_cmds.get(category, []) + hdict_db.get(category, []))) entries = sorted(set(hdict_cmds.get(category, []) + hdict_db.get(category, [])))
@ -281,8 +261,8 @@ class CmdHelp(Command):
] ]
all_categories = list( all_categories = list(
set( set(
[HelpCategory(cmd.help_category) for cmd in all_cmds] + [HelpCategory(cmd.help_category) for cmd in all_cmds]
[HelpCategory(topic.help_category) for topic in all_topics] + [HelpCategory(topic.help_category) for topic in all_topics]
) )
) )
@ -302,38 +282,47 @@ class CmdHelp(Command):
return return
# Try to access a particular help entry or category # Try to access a particular help entry or category
entries = ([cmd for cmd in all_cmds if cmd] + entries = [cmd for cmd in all_cmds if cmd] + list(HelpEntry.objects.all()) + all_categories
list(HelpEntry.objects.all()) +
all_categories)
for match_query in [f"{query}~1", f"{query}*"]: for match_query in [f"{query}~1", f"{query}*"]:
# We first do an exact word-match followed by a start-by query # We first do an exact word-match followed by a start-by query
matches, suggestions = help_search_with_index( matches, suggestions = help_search_with_index(
match_query, entries, suggestion_maxnum=self.suggestion_maxnum) match_query, entries, suggestion_maxnum=self.suggestion_maxnum
)
if matches: if matches:
match = matches[0] match = matches[0]
if isinstance(match, HelpCategory): if isinstance(match, HelpCategory):
formatted = self.format_help_list( formatted = self.format_help_list(
{match.key: [cmd.key for cmd in all_cmds {
if match.key.lower() == cmd.help_category]}, match.key: [
{match.key: [topic.key for topic in all_topics cmd.key
if match.key.lower() == topic.help_category]} for cmd in all_cmds
if match.key.lower() == cmd.help_category
]
},
{
match.key: [
topic.key
for topic in all_topics
if match.key.lower() == topic.help_category
]
},
) )
elif inherits_from(match, "evennia.commands.command.Command"): elif inherits_from(match, "evennia.commands.command.Command"):
formatted = self.format_help_entry( formatted = self.format_help_entry(
match.key, match.key,
match.get_help(caller, cmdset), match.get_help(caller, cmdset),
aliases=match.aliases, aliases=match.aliases,
suggested=suggestions[1:] suggested=suggestions[1:],
) )
else: else:
formatted = self.format_help_entry( formatted = self.format_help_entry(
match.key, match.key,
match.entrytext, match.entrytext,
aliases=match.aliases.all(), aliases=match.aliases.all(),
suggested=suggestions[1:] suggested=suggestions[1:],
) )
self.msg_help(formatted) self.msg_help(formatted)

View file

@ -1,3 +1,3 @@
""" """
Intended to be a collecting folder for Django-specific contribs that do not have observable effects to players. Intended to be a collecting folder for Django-specific contribs that do not have observable effects to players.
""" """

View file

@ -420,7 +420,7 @@ class S3Boto3StorageFile(File):
if "r" in mode and "w" in mode: if "r" in mode and "w" in mode:
raise ValueError("Can't combine 'r' and 'w' in mode.") raise ValueError("Can't combine 'r' and 'w' in mode.")
self._storage = storage self._storage = storage
self.name = name[len(self._storage.location):].lstrip("/") self.name = name[len(self._storage.location) :].lstrip("/")
self._mode = mode self._mode = mode
self._force_mode = (lambda b: b) if "b" in mode else force_text self._force_mode = (lambda b: b) if "b" in mode else force_text
self.obj = storage.bucket.Object(storage._encode_name(name)) self.obj = storage.bucket.Object(storage._encode_name(name))

View file

@ -30,7 +30,6 @@ class S3Boto3TestCase(TestCase):
class S3Boto3StorageTests(S3Boto3TestCase): class S3Boto3StorageTests(S3Boto3TestCase):
def test_clean_name(self): def test_clean_name(self):
""" """
Test the base case of _clean_name Test the base case of _clean_name
@ -94,87 +93,75 @@ class S3Boto3StorageTests(S3Boto3TestCase):
""" """
Test URL generation. Test URL generation.
""" """
self.storage.custom_domain = 'example.com' self.storage.custom_domain = "example.com"
# We expect no leading slashes in the path, # We expect no leading slashes in the path,
# and trailing slashes should be preserved. # and trailing slashes should be preserved.
self.assertEqual(self.storage.url(''), 'https://example.com/') self.assertEqual(self.storage.url(""), "https://example.com/")
self.assertEqual(self.storage.url('path'), 'https://example.com/path') self.assertEqual(self.storage.url("path"), "https://example.com/path")
self.assertEqual(self.storage.url('path/'), 'https://example.com/path/') self.assertEqual(self.storage.url("path/"), "https://example.com/path/")
self.assertEqual(self.storage.url('path/1'), 'https://example.com/path/1') self.assertEqual(self.storage.url("path/1"), "https://example.com/path/1")
self.assertEqual(self.storage.url('path/1/'), 'https://example.com/path/1/') self.assertEqual(self.storage.url("path/1/"), "https://example.com/path/1/")
def test_storage_save(self): def test_storage_save(self):
""" """
Test saving a file Test saving a file
""" """
name = 'test_storage_save.txt' name = "test_storage_save.txt"
content = ContentFile('new content') content = ContentFile("new content")
self.storage.save(name, content) self.storage.save(name, content)
self.storage.bucket.Object.assert_called_once_with(name) self.storage.bucket.Object.assert_called_once_with(name)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with( obj.upload_fileobj.assert_called_with(
content, content, ExtraArgs={"ContentType": "text/plain", "ACL": self.storage.default_acl,}
ExtraArgs={
'ContentType': 'text/plain',
'ACL': self.storage.default_acl,
}
) )
def test_storage_save_with_acl(self): def test_storage_save_with_acl(self):
""" """
Test saving a file with user defined ACL. Test saving a file with user defined ACL.
""" """
name = 'test_storage_save.txt' name = "test_storage_save.txt"
content = ContentFile('new content') content = ContentFile("new content")
self.storage.default_acl = 'private' self.storage.default_acl = "private"
self.storage.save(name, content) self.storage.save(name, content)
self.storage.bucket.Object.assert_called_once_with(name) self.storage.bucket.Object.assert_called_once_with(name)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with( obj.upload_fileobj.assert_called_with(
content, content, ExtraArgs={"ContentType": "text/plain", "ACL": "private",}
ExtraArgs={
'ContentType': 'text/plain',
'ACL': 'private',
}
) )
def test_content_type(self): def test_content_type(self):
""" """
Test saving a file with a None content type. Test saving a file with a None content type.
""" """
name = 'test_image.jpg' name = "test_image.jpg"
content = ContentFile('data') content = ContentFile("data")
content.content_type = None content.content_type = None
self.storage.save(name, content) self.storage.save(name, content)
self.storage.bucket.Object.assert_called_once_with(name) self.storage.bucket.Object.assert_called_once_with(name)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with( obj.upload_fileobj.assert_called_with(
content, content, ExtraArgs={"ContentType": "image/jpeg", "ACL": self.storage.default_acl,}
ExtraArgs={
'ContentType': 'image/jpeg',
'ACL': self.storage.default_acl,
}
) )
def test_storage_save_gzipped(self): def test_storage_save_gzipped(self):
""" """
Test saving a gzipped file Test saving a gzipped file
""" """
name = 'test_storage_save.gz' name = "test_storage_save.gz"
content = ContentFile("I am gzip'd") content = ContentFile("I am gzip'd")
self.storage.save(name, content) self.storage.save(name, content)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with( obj.upload_fileobj.assert_called_with(
content, content,
ExtraArgs={ ExtraArgs={
'ContentType': 'application/octet-stream', "ContentType": "application/octet-stream",
'ContentEncoding': 'gzip', "ContentEncoding": "gzip",
'ACL': self.storage.default_acl, "ACL": self.storage.default_acl,
} },
) )
def test_storage_save_gzip(self): def test_storage_save_gzip(self):
@ -182,21 +169,21 @@ class S3Boto3StorageTests(S3Boto3TestCase):
Test saving a file with gzip enabled. Test saving a file with gzip enabled.
""" """
self.storage.gzip = True self.storage.gzip = True
name = 'test_storage_save.css' name = "test_storage_save.css"
content = ContentFile("I should be gzip'd") content = ContentFile("I should be gzip'd")
self.storage.save(name, content) self.storage.save(name, content)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with( obj.upload_fileobj.assert_called_with(
mock.ANY, mock.ANY,
ExtraArgs={ ExtraArgs={
'ContentType': 'text/css', "ContentType": "text/css",
'ContentEncoding': 'gzip', "ContentEncoding": "gzip",
'ACL': self.storage.default_acl, "ACL": self.storage.default_acl,
} },
) )
args, kwargs = obj.upload_fileobj.call_args args, kwargs = obj.upload_fileobj.call_args
content = args[0] content = args[0]
zfile = gzip.GzipFile(mode='rb', fileobj=content) zfile = gzip.GzipFile(mode="rb", fileobj=content)
self.assertEqual(zfile.read(), b"I should be gzip'd") self.assertEqual(zfile.read(), b"I should be gzip'd")
def test_storage_save_gzip_twice(self): def test_storage_save_gzip_twice(self):
@ -205,26 +192,26 @@ class S3Boto3StorageTests(S3Boto3TestCase):
""" """
# Given # Given
self.storage.gzip = True self.storage.gzip = True
name = 'test_storage_save.css' name = "test_storage_save.css"
content = ContentFile("I should be gzip'd") content = ContentFile("I should be gzip'd")
# When # When
self.storage.save(name, content) self.storage.save(name, content)
self.storage.save('test_storage_save_2.css', content) self.storage.save("test_storage_save_2.css", content)
# Then # Then
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with( obj.upload_fileobj.assert_called_with(
mock.ANY, mock.ANY,
ExtraArgs={ ExtraArgs={
'ContentType': 'text/css', "ContentType": "text/css",
'ContentEncoding': 'gzip', "ContentEncoding": "gzip",
'ACL': self.storage.default_acl, "ACL": self.storage.default_acl,
} },
) )
args, kwargs = obj.upload_fileobj.call_args args, kwargs = obj.upload_fileobj.call_args
content = args[0] content = args[0]
zfile = gzip.GzipFile(mode='rb', fileobj=content) zfile = gzip.GzipFile(mode="rb", fileobj=content)
self.assertEqual(zfile.read(), b"I should be gzip'd") self.assertEqual(zfile.read(), b"I should be gzip'd")
def test_compress_content_len(self): def test_compress_content_len(self):
@ -240,15 +227,15 @@ class S3Boto3StorageTests(S3Boto3TestCase):
""" """
Test opening a file in write mode Test opening a file in write mode
""" """
name = 'test_open_for_writïng.txt' name = "test_open_for_writïng.txt"
content = 'new content' content = "new content"
# Set the encryption flag used for multipart uploads # Set the encryption flag used for multipart uploads
self.storage.encryption = True self.storage.encryption = True
self.storage.reduced_redundancy = True self.storage.reduced_redundancy = True
self.storage.default_acl = 'public-read' self.storage.default_acl = "public-read"
file = self.storage.open(name, 'w') file = self.storage.open(name, "w")
self.storage.bucket.Object.assert_called_with(name) self.storage.bucket.Object.assert_called_with(name)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
# Set the name of the mock object # Set the name of the mock object
@ -256,21 +243,22 @@ class S3Boto3StorageTests(S3Boto3TestCase):
file.write(content) file.write(content)
obj.initiate_multipart_upload.assert_called_with( obj.initiate_multipart_upload.assert_called_with(
ACL='public-read', ACL="public-read",
ContentType='text/plain', ContentType="text/plain",
ServerSideEncryption='AES256', ServerSideEncryption="AES256",
StorageClass='REDUCED_REDUNDANCY' StorageClass="REDUCED_REDUNDANCY",
) )
# Save the internal file before closing # Save the internal file before closing
multipart = obj.initiate_multipart_upload.return_value multipart = obj.initiate_multipart_upload.return_value
multipart.parts.all.return_value = [mock.MagicMock(e_tag='123', part_number=1)] multipart.parts.all.return_value = [mock.MagicMock(e_tag="123", part_number=1)]
file.close() file.close()
multipart.Part.assert_called_with(1) multipart.Part.assert_called_with(1)
part = multipart.Part.return_value part = multipart.Part.return_value
part.upload.assert_called_with(Body=content.encode('utf-8')) part.upload.assert_called_with(Body=content.encode("utf-8"))
multipart.complete.assert_called_once_with( multipart.complete.assert_called_once_with(
MultipartUpload={'Parts': [{'ETag': '123', 'PartNumber': 1}]}) MultipartUpload={"Parts": [{"ETag": "123", "PartNumber": 1}]}
)
def test_storage_open_no_write(self): def test_storage_open_no_write(self):
""" """
@ -278,19 +266,19 @@ class S3Boto3StorageTests(S3Boto3TestCase):
A file should be created as by obj.put(...). A file should be created as by obj.put(...).
""" """
name = 'test_open_no_write.txt' name = "test_open_no_write.txt"
# Set the encryption flag used for puts # Set the encryption flag used for puts
self.storage.encryption = True self.storage.encryption = True
self.storage.reduced_redundancy = True self.storage.reduced_redundancy = True
self.storage.default_acl = 'public-read' self.storage.default_acl = "public-read"
file = self.storage.open(name, 'w') file = self.storage.open(name, "w")
self.storage.bucket.Object.assert_called_with(name) self.storage.bucket.Object.assert_called_with(name)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.load.side_effect = ClientError({'Error': {}, obj.load.side_effect = ClientError(
'ResponseMetadata': {'HTTPStatusCode': 404}}, {"Error": {}, "ResponseMetadata": {"HTTPStatusCode": 404}}, "head_bucket"
'head_bucket') )
# Set the name of the mock object # Set the name of the mock object
obj.key = name obj.key = name
@ -300,25 +288,25 @@ class S3Boto3StorageTests(S3Boto3TestCase):
obj.load.assert_called_once_with() obj.load.assert_called_once_with()
obj.put.assert_called_once_with( obj.put.assert_called_once_with(
ACL='public-read', ACL="public-read",
Body=b"", Body=b"",
ContentType='text/plain', ContentType="text/plain",
ServerSideEncryption='AES256', ServerSideEncryption="AES256",
StorageClass='REDUCED_REDUNDANCY' StorageClass="REDUCED_REDUNDANCY",
) )
def test_storage_open_no_overwrite_existing(self): def test_storage_open_no_overwrite_existing(self):
""" """
Test opening an existing file in write mode and closing without writing. Test opening an existing file in write mode and closing without writing.
""" """
name = 'test_open_no_overwrite_existing.txt' name = "test_open_no_overwrite_existing.txt"
# Set the encryption flag used for puts # Set the encryption flag used for puts
self.storage.encryption = True self.storage.encryption = True
self.storage.reduced_redundancy = True self.storage.reduced_redundancy = True
self.storage.default_acl = 'public-read' self.storage.default_acl = "public-read"
file = self.storage.open(name, 'w') file = self.storage.open(name, "w")
self.storage.bucket.Object.assert_called_with(name) self.storage.bucket.Object.assert_called_with(name)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
@ -335,125 +323,111 @@ class S3Boto3StorageTests(S3Boto3TestCase):
""" """
Test writing content that exceeds the buffer size Test writing content that exceeds the buffer size
""" """
name = 'test_open_for_writïng_beyond_buffer_size.txt' name = "test_open_for_writïng_beyond_buffer_size.txt"
# Set the encryption flag used for multipart uploads # Set the encryption flag used for multipart uploads
self.storage.encryption = True self.storage.encryption = True
self.storage.reduced_redundancy = True self.storage.reduced_redundancy = True
self.storage.default_acl = 'public-read' self.storage.default_acl = "public-read"
file = self.storage.open(name, 'w') file = self.storage.open(name, "w")
self.storage.bucket.Object.assert_called_with(name) self.storage.bucket.Object.assert_called_with(name)
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
# Set the name of the mock object # Set the name of the mock object
obj.key = name obj.key = name
# Initiate the multipart upload # Initiate the multipart upload
file.write('') file.write("")
obj.initiate_multipart_upload.assert_called_with( obj.initiate_multipart_upload.assert_called_with(
ACL='public-read', ACL="public-read",
ContentType='text/plain', ContentType="text/plain",
ServerSideEncryption='AES256', ServerSideEncryption="AES256",
StorageClass='REDUCED_REDUNDANCY' StorageClass="REDUCED_REDUNDANCY",
) )
multipart = obj.initiate_multipart_upload.return_value multipart = obj.initiate_multipart_upload.return_value
# Write content at least twice as long as the buffer size # Write content at least twice as long as the buffer size
written_content = '' written_content = ""
counter = 1 counter = 1
while len(written_content) < 2 * file.buffer_size: while len(written_content) < 2 * file.buffer_size:
content = 'hello, aws {counter}\n'.format(counter=counter) content = "hello, aws {counter}\n".format(counter=counter)
# Write more than just a few bytes in each iteration to keep the # Write more than just a few bytes in each iteration to keep the
# test reasonably fast # test reasonably fast
content += '*' * int(file.buffer_size / 10) content += "*" * int(file.buffer_size / 10)
file.write(content) file.write(content)
written_content += content written_content += content
counter += 1 counter += 1
# Save the internal file before closing # Save the internal file before closing
multipart.parts.all.return_value = [ multipart.parts.all.return_value = [
mock.MagicMock(e_tag='123', part_number=1), mock.MagicMock(e_tag="123", part_number=1),
mock.MagicMock(e_tag='456', part_number=2) mock.MagicMock(e_tag="456", part_number=2),
] ]
file.close() file.close()
self.assertListEqual( self.assertListEqual(multipart.Part.call_args_list, [mock.call(1), mock.call(2)])
multipart.Part.call_args_list,
[mock.call(1), mock.call(2)]
)
part = multipart.Part.return_value part = multipart.Part.return_value
uploaded_content = ''.join( uploaded_content = "".join(
args_list[1]['Body'].decode('utf-8') args_list[1]["Body"].decode("utf-8") for args_list in part.upload.call_args_list
for args_list in part.upload.call_args_list
) )
self.assertEqual(uploaded_content, written_content) self.assertEqual(uploaded_content, written_content)
multipart.complete.assert_called_once_with( multipart.complete.assert_called_once_with(
MultipartUpload={'Parts': [ MultipartUpload={
{'ETag': '123', 'PartNumber': 1}, "Parts": [{"ETag": "123", "PartNumber": 1}, {"ETag": "456", "PartNumber": 2},]
{'ETag': '456', 'PartNumber': 2}, }
]}
) )
def test_auto_creating_bucket(self): def test_auto_creating_bucket(self):
self.storage.auto_create_bucket = True self.storage.auto_create_bucket = True
Bucket = mock.MagicMock() Bucket = mock.MagicMock()
self.storage._connections.connection.Bucket.return_value = Bucket self.storage._connections.connection.Bucket.return_value = Bucket
self.storage._connections.connection.meta.client.meta.region_name = 'sa-east-1' self.storage._connections.connection.meta.client.meta.region_name = "sa-east-1"
Bucket.meta.client.head_bucket.side_effect = ClientError({'Error': {}, Bucket.meta.client.head_bucket.side_effect = ClientError(
'ResponseMetadata': {'HTTPStatusCode': 404}}, {"Error": {}, "ResponseMetadata": {"HTTPStatusCode": 404}}, "head_bucket"
'head_bucket') )
self.storage._get_or_create_bucket('testbucketname') self.storage._get_or_create_bucket("testbucketname")
Bucket.create.assert_called_once_with( Bucket.create.assert_called_once_with(
ACL='public-read', ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
CreateBucketConfiguration={
'LocationConstraint': 'sa-east-1',
}
) )
def test_auto_creating_bucket_with_acl(self): def test_auto_creating_bucket_with_acl(self):
self.storage.auto_create_bucket = True self.storage.auto_create_bucket = True
self.storage.bucket_acl = 'public-read' self.storage.bucket_acl = "public-read"
Bucket = mock.MagicMock() Bucket = mock.MagicMock()
self.storage._connections.connection.Bucket.return_value = Bucket self.storage._connections.connection.Bucket.return_value = Bucket
self.storage._connections.connection.meta.client.meta.region_name = 'sa-east-1' self.storage._connections.connection.meta.client.meta.region_name = "sa-east-1"
Bucket.meta.client.head_bucket.side_effect = ClientError({'Error': {}, Bucket.meta.client.head_bucket.side_effect = ClientError(
'ResponseMetadata': {'HTTPStatusCode': 404}}, {"Error": {}, "ResponseMetadata": {"HTTPStatusCode": 404}}, "head_bucket"
'head_bucket') )
self.storage._get_or_create_bucket('testbucketname') self.storage._get_or_create_bucket("testbucketname")
Bucket.create.assert_called_once_with( Bucket.create.assert_called_once_with(
ACL='public-read', ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
CreateBucketConfiguration={
'LocationConstraint': 'sa-east-1',
}
) )
def test_storage_exists(self): def test_storage_exists(self):
self.assertTrue(self.storage.exists("file.txt")) self.assertTrue(self.storage.exists("file.txt"))
self.storage.connection.meta.client.head_object.assert_called_with( self.storage.connection.meta.client.head_object.assert_called_with(
Bucket=self.storage.bucket_name, Bucket=self.storage.bucket_name, Key="file.txt",
Key="file.txt",
) )
def test_storage_exists_false(self): def test_storage_exists_false(self):
self.storage.connection.meta.client.head_object.side_effect = ClientError( self.storage.connection.meta.client.head_object.side_effect = ClientError(
{'Error': {'Code': '404', 'Message': 'Not Found'}}, {"Error": {"Code": "404", "Message": "Not Found"}}, "HeadObject",
'HeadObject',
) )
self.assertFalse(self.storage.exists("file.txt")) self.assertFalse(self.storage.exists("file.txt"))
self.storage.connection.meta.client.head_object.assert_called_with( self.storage.connection.meta.client.head_object.assert_called_with(
Bucket=self.storage.bucket_name, Bucket=self.storage.bucket_name, Key="file.txt",
Key='file.txt',
) )
def test_storage_exists_doesnt_create_bucket(self): def test_storage_exists_doesnt_create_bucket(self):
with mock.patch.object(self.storage, '_get_or_create_bucket') as method: with mock.patch.object(self.storage, "_get_or_create_bucket") as method:
self.storage.exists('file.txt') self.storage.exists("file.txt")
self.assertFalse(method.called) self.assertFalse(method.called)
def test_storage_delete(self): def test_storage_delete(self):
self.storage.delete("path/to/file.txt") self.storage.delete("path/to/file.txt")
self.storage.bucket.Object.assert_called_with('path/to/file.txt') self.storage.bucket.Object.assert_called_with("path/to/file.txt")
self.storage.bucket.Object.return_value.delete.assert_called_with() self.storage.bucket.Object.return_value.delete.assert_called_with()
def test_storage_listdir_base(self): def test_storage_listdir_base(self):
@ -464,14 +438,8 @@ class S3Boto3StorageTests(S3Boto3TestCase):
# 4.txt # 4.txt
pages = [ pages = [
{ {
'CommonPrefixes': [ "CommonPrefixes": [{"Prefix": "some"}, {"Prefix": "other"},],
{'Prefix': 'some'}, "Contents": [{"Key": "2.txt"}, {"Key": "4.txt"},],
{'Prefix': 'other'},
],
'Contents': [
{'Key': '2.txt'},
{'Key': '4.txt'},
],
}, },
] ]
@ -479,42 +447,35 @@ class S3Boto3StorageTests(S3Boto3TestCase):
paginator.paginate.return_value = pages paginator.paginate.return_value = pages
self.storage._connections.connection.meta.client.get_paginator.return_value = paginator self.storage._connections.connection.meta.client.get_paginator.return_value = paginator
dirs, files = self.storage.listdir('') dirs, files = self.storage.listdir("")
paginator.paginate.assert_called_with(Bucket=None, Delimiter='/', Prefix='') paginator.paginate.assert_called_with(Bucket=None, Delimiter="/", Prefix="")
self.assertEqual(dirs, ['some', 'other']) self.assertEqual(dirs, ["some", "other"])
self.assertEqual(files, ['2.txt', '4.txt']) self.assertEqual(files, ["2.txt", "4.txt"])
def test_storage_listdir_subdir(self): def test_storage_listdir_subdir(self):
# Files: # Files:
# some/path/1.txt # some/path/1.txt
# some/2.txt # some/2.txt
pages = [ pages = [
{ {"CommonPrefixes": [{"Prefix": "some/path"},], "Contents": [{"Key": "some/2.txt"},],},
'CommonPrefixes': [
{'Prefix': 'some/path'},
],
'Contents': [
{'Key': 'some/2.txt'},
],
},
] ]
paginator = mock.MagicMock() paginator = mock.MagicMock()
paginator.paginate.return_value = pages paginator.paginate.return_value = pages
self.storage._connections.connection.meta.client.get_paginator.return_value = paginator self.storage._connections.connection.meta.client.get_paginator.return_value = paginator
dirs, files = self.storage.listdir('some/') dirs, files = self.storage.listdir("some/")
paginator.paginate.assert_called_with(Bucket=None, Delimiter='/', Prefix='some/') paginator.paginate.assert_called_with(Bucket=None, Delimiter="/", Prefix="some/")
self.assertEqual(dirs, ['path']) self.assertEqual(dirs, ["path"])
self.assertEqual(files, ['2.txt']) self.assertEqual(files, ["2.txt"])
def test_storage_size(self): def test_storage_size(self):
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.content_length = 4098 obj.content_length = 4098
name = 'file.txt' name = "file.txt"
self.assertEqual(self.storage.size(name), obj.content_length) self.assertEqual(self.storage.size(name), obj.content_length)
def test_storage_mtime(self): def test_storage_mtime(self):
@ -527,30 +488,29 @@ class S3Boto3StorageTests(S3Boto3TestCase):
obj = self.storage.bucket.Object.return_value obj = self.storage.bucket.Object.return_value
obj.last_modified = datetime.datetime.now(utc) obj.last_modified = datetime.datetime.now(utc)
name = 'file.txt' name = "file.txt"
self.assertFalse( self.assertFalse(
is_aware(self.storage.modified_time(name)), is_aware(self.storage.modified_time(name)),
'Naive datetime object expected from modified_time()' "Naive datetime object expected from modified_time()",
) )
self.assertIs( self.assertIs(
settings.USE_TZ, settings.USE_TZ,
is_aware(self.storage.get_modified_time(name)), is_aware(self.storage.get_modified_time(name)),
'{} datetime object expected from get_modified_time() when USE_TZ={}'.format( "{} datetime object expected from get_modified_time() when USE_TZ={}".format(
('Naive', 'Aware')[settings.USE_TZ], ("Naive", "Aware")[settings.USE_TZ], settings.USE_TZ
settings.USE_TZ ),
)
) )
def test_storage_url(self): def test_storage_url(self):
name = 'test_storage_size.txt' name = "test_storage_size.txt"
url = 'http://aws.amazon.com/%s' % name url = "http://aws.amazon.com/%s" % name
self.storage.bucket.meta.client.generate_presigned_url.return_value = url self.storage.bucket.meta.client.generate_presigned_url.return_value = url
self.storage.bucket.name = 'bucket' self.storage.bucket.name = "bucket"
self.assertEqual(self.storage.url(name), url) self.assertEqual(self.storage.url(name), url)
self.storage.bucket.meta.client.generate_presigned_url.assert_called_with( self.storage.bucket.meta.client.generate_presigned_url.assert_called_with(
'get_object', "get_object",
Params={'Bucket': self.storage.bucket.name, 'Key': name}, Params={"Bucket": self.storage.bucket.name, "Key": name},
ExpiresIn=self.storage.querystring_expire, ExpiresIn=self.storage.querystring_expire,
) )
@ -558,26 +518,24 @@ class S3Boto3StorageTests(S3Boto3TestCase):
self.assertEqual(self.storage.url(name, expire=custom_expire), url) self.assertEqual(self.storage.url(name, expire=custom_expire), url)
self.storage.bucket.meta.client.generate_presigned_url.assert_called_with( self.storage.bucket.meta.client.generate_presigned_url.assert_called_with(
'get_object', "get_object",
Params={'Bucket': self.storage.bucket.name, 'Key': name}, Params={"Bucket": self.storage.bucket.name, "Key": name},
ExpiresIn=custom_expire, ExpiresIn=custom_expire,
) )
def test_generated_url_is_encoded(self): def test_generated_url_is_encoded(self):
self.storage.custom_domain = "mock.cloudfront.net" self.storage.custom_domain = "mock.cloudfront.net"
filename = "whacky & filename.mp4" filename = "whacky & filename.mp4"
url = self.storage.url(filename) url = self.storage.url(filename)
parsed_url = urlparse.urlparse(url) parsed_url = urlparse.urlparse(url)
self.assertEqual(parsed_url.path, self.assertEqual(parsed_url.path, "/whacky%20%26%20filename.mp4")
"/whacky%20%26%20filename.mp4")
self.assertFalse(self.storage.bucket.meta.client.generate_presigned_url.called) self.assertFalse(self.storage.bucket.meta.client.generate_presigned_url.called)
def test_special_characters(self): def test_special_characters(self):
self.storage.custom_domain = "mock.cloudfront.net" self.storage.custom_domain = "mock.cloudfront.net"
name = "ãlöhâ.jpg" name = "ãlöhâ.jpg"
content = ContentFile('new content') content = ContentFile("new content")
self.storage.save(name, content) self.storage.save(name, content)
self.storage.bucket.Object.assert_called_once_with(name) self.storage.bucket.Object.assert_called_once_with(name)
@ -586,13 +544,21 @@ class S3Boto3StorageTests(S3Boto3TestCase):
self.assertEqual(parsed_url.path, "/%C3%A3l%C3%B6h%C3%A2.jpg") self.assertEqual(parsed_url.path, "/%C3%A3l%C3%B6h%C3%A2.jpg")
def test_strip_signing_parameters(self): def test_strip_signing_parameters(self):
expected = 'http://bucket.s3-aws-region.amazonaws.com/foo/bar' expected = "http://bucket.s3-aws-region.amazonaws.com/foo/bar"
self.assertEqual(self.storage._strip_signing_parameters( self.assertEqual(
'%s?X-Amz-Date=12345678&X-Amz-Signature=Signature' % expected), expected) self.storage._strip_signing_parameters(
self.assertEqual(self.storage._strip_signing_parameters( "%s?X-Amz-Date=12345678&X-Amz-Signature=Signature" % expected
'%s?expires=12345678&signature=Signature' % expected), expected) ),
expected,
)
self.assertEqual(
self.storage._strip_signing_parameters(
"%s?expires=12345678&signature=Signature" % expected
),
expected,
)
@skipIf(threading is None, 'Test requires threading') @skipIf(threading is None, "Test requires threading")
def test_connection_threading(self): def test_connection_threading(self):
connections = [] connections = []
@ -613,23 +579,23 @@ class S3Boto3StorageTests(S3Boto3TestCase):
"Found '/'. Use '' instead." "Found '/'. Use '' instead."
) )
with self.assertRaises(ImproperlyConfigured, msg=msg): with self.assertRaises(ImproperlyConfigured, msg=msg):
s3boto3.S3Boto3Storage(location='/') s3boto3.S3Boto3Storage(location="/")
def test_override_class_variable(self): def test_override_class_variable(self):
class MyStorage1(s3boto3.S3Boto3Storage): class MyStorage1(s3boto3.S3Boto3Storage):
location = 'foo1' location = "foo1"
storage = MyStorage1() storage = MyStorage1()
self.assertEqual(storage.location, 'foo1') self.assertEqual(storage.location, "foo1")
class MyStorage2(s3boto3.S3Boto3Storage): class MyStorage2(s3boto3.S3Boto3Storage):
location = 'foo2' location = "foo2"
storage = MyStorage2() storage = MyStorage2()
self.assertEqual(storage.location, 'foo2') self.assertEqual(storage.location, "foo2")
def test_override_init_argument(self): def test_override_init_argument(self):
storage = s3boto3.S3Boto3Storage(location='foo1') storage = s3boto3.S3Boto3Storage(location="foo1")
self.assertEqual(storage.location, 'foo1') self.assertEqual(storage.location, "foo1")
storage = s3boto3.S3Boto3Storage(location='foo2') storage = s3boto3.S3Boto3Storage(location="foo2")
self.assertEqual(storage.location, 'foo2') self.assertEqual(storage.location, "foo2")

View file

@ -74,7 +74,7 @@ class HelpEntry(SharedMemoryModel):
Tag, Tag,
blank=True, blank=True,
help_text="tags on this object. Tags are simple string markers to " help_text="tags on this object. Tags are simple string markers to "
"identify, group and alias objects.", "identify, group and alias objects.",
) )
# (deprecated, only here to allow MUX helpfile load (don't use otherwise)). # (deprecated, only here to allow MUX helpfile load (don't use otherwise)).
# TODO: remove this when not needed anymore. # TODO: remove this when not needed anymore.
@ -134,7 +134,7 @@ class HelpEntry(SharedMemoryModel):
"aliases": " ".join(self.aliases.all()), "aliases": " ".join(self.aliases.all()),
"category": self.db_help_category, "category": self.db_help_category,
"text": self.db_entrytext, "text": self.db_entrytext,
"tags": " ".join(str(tag) for tag in self.tags.all()) "tags": " ".join(str(tag) for tag in self.tags.all()),
} }
# #

View file

@ -2134,6 +2134,7 @@ class DefaultCharacter(DefaultObject):
""" """
from evennia.utils.utils import latinify from evennia.utils.utils import latinify
latin_name = latinify(name, default="X") latin_name = latinify(name, default="X")
return latin_name return latin_name
@ -2148,7 +2149,7 @@ class DefaultCharacter(DefaultObject):
""" """
return True # Default validator does not perform any operations return True # Default validator does not perform any operations
def basetype_setup(self): def basetype_setup(self):
""" """

View file

@ -40,7 +40,9 @@ class DefaultObjectTest(EvenniaTest):
self.assertEqual(obj.db_home, self.room1) self.assertEqual(obj.db_home, self.room1)
def test_character_create_weirdname(self): def test_character_create_weirdname(self):
obj, errors = DefaultCharacter.create("SigurðurÞórarinsson", self.account, home=self.room1.dbref) obj, errors = DefaultCharacter.create(
"SigurðurÞórarinsson", self.account, home=self.room1.dbref
)
self.assertTrue(obj, errors) self.assertTrue(obj, errors)
self.assertFalse(errors, errors) self.assertFalse(errors, errors)
self.assertEqual(obj.name, "SigurXurXorarinsson") self.assertEqual(obj.name, "SigurXurXorarinsson")

View file

@ -255,9 +255,7 @@ IN_GAME_ERRORS = True
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.sqlite3", "ENGINE": "django.db.backends.sqlite3",
"NAME": os.getenv( "NAME": os.getenv("TEST_DB_PATH", os.path.join(GAME_DIR, "server", "evennia.db3")),
"TEST_DB_PATH", os.path.join(GAME_DIR, "server", "evennia.db3")
),
"USER": "", "USER": "",
"PASSWORD": "", "PASSWORD": "",
"HOST": "", "HOST": "",
@ -872,9 +870,7 @@ TEMPLATES = [
os.path.join(GAME_DIR, "web", "template_overrides"), os.path.join(GAME_DIR, "web", "template_overrides"),
os.path.join(EVENNIA_DIR, "web", "website", "templates", WEBSITE_TEMPLATE), os.path.join(EVENNIA_DIR, "web", "website", "templates", WEBSITE_TEMPLATE),
os.path.join(EVENNIA_DIR, "web", "website", "templates"), os.path.join(EVENNIA_DIR, "web", "website", "templates"),
os.path.join( os.path.join(EVENNIA_DIR, "web", "webclient", "templates", WEBCLIENT_TEMPLATE),
EVENNIA_DIR, "web", "webclient", "templates", WEBCLIENT_TEMPLATE
),
os.path.join(EVENNIA_DIR, "web", "webclient", "templates"), os.path.join(EVENNIA_DIR, "web", "webclient", "templates"),
], ],
"APP_DIRS": True, "APP_DIRS": True,
@ -946,9 +942,7 @@ AUTH_USER_MODEL = "accounts.AccountDB"
# Password validation plugins # Password validation plugins
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
},
{ {
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {"min_length": 8}, "OPTIONS": {"min_length": 8},
@ -961,14 +955,8 @@ AUTH_PASSWORD_VALIDATORS = [
# Username validation plugins # Username validation plugins
AUTH_USERNAME_VALIDATORS = [ AUTH_USERNAME_VALIDATORS = [
{"NAME": "django.contrib.auth.validators.ASCIIUsernameValidator"}, {"NAME": "django.contrib.auth.validators.ASCIIUsernameValidator"},
{ {"NAME": "django.core.validators.MinLengthValidator", "OPTIONS": {"limit_value": 3},},
"NAME": "django.core.validators.MinLengthValidator", {"NAME": "django.core.validators.MaxLengthValidator", "OPTIONS": {"limit_value": 30},},
"OPTIONS": {"limit_value": 3},
},
{
"NAME": "django.core.validators.MaxLengthValidator",
"OPTIONS": {"limit_value": 30},
},
{"NAME": "evennia.server.validators.EvenniaUsernameAvailabilityValidator"}, {"NAME": "evennia.server.validators.EvenniaUsernameAvailabilityValidator"},
] ]
@ -982,22 +970,18 @@ MESSAGE_TAGS = {messages.ERROR: "danger"}
# Django REST Framework settings # Django REST Framework settings
REST_FRAMEWORK = { REST_FRAMEWORK = {
# django_filters allows you to specify search fields for models in an API View # django_filters allows you to specify search fields for models in an API View
'DEFAULT_FILTER_BACKENDS': ( "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
'django_filters.rest_framework.DjangoFilterBackend',
),
# whether to paginate results and how many per page # whether to paginate results and how many per page
"DEFAULT_PAGINATION_CLASS": 'rest_framework.pagination.LimitOffsetPagination', "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
'PAGE_SIZE': 25, "PAGE_SIZE": 25,
# require logged in users to call API so that access checks can work on them # require logged in users to call API so that access checks can work on them
'DEFAULT_PERMISSION_CLASSES': [ "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated",],
'rest_framework.permissions.IsAuthenticated',
],
# These are the different ways people can authenticate for API requests - via # These are the different ways people can authenticate for API requests - via
# session or with user/password. Other ways are possible, such as via tokens # session or with user/password. Other ways are possible, such as via tokens
# or oauth, but require additional dependencies. # or oauth, but require additional dependencies.
'DEFAULT_AUTHENTICATION_CLASSES': [ "DEFAULT_AUTHENTICATION_CLASSES": [
'rest_framework.authentication.BasicAuthentication', "rest_framework.authentication.BasicAuthentication",
'rest_framework.authentication.SessionAuthentication', "rest_framework.authentication.SessionAuthentication",
], ],
# default permission checks used by the EvenniaPermission class # default permission checks used by the EvenniaPermission class
"DEFAULT_CREATE_PERMISSION": "builder", "DEFAULT_CREATE_PERMISSION": "builder",

View file

@ -335,8 +335,9 @@ class EvMore(object):
# no justification. Simple division by line # no justification. Simple division by line
lines = text.split("\n") lines = text.split("\n")
self._data = [_LBR.join(lines[i: i + self.height]) self._data = [
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) self._npages = len(self._data)
self._paginator = self.paginator_index self._paginator = self.paginator_index

View file

@ -246,6 +246,7 @@ class LatinifyTest(TestCase):
class TestFormatGrid(TestCase): class TestFormatGrid(TestCase):
maxDiff = None maxDiff = None
def setUp(self): def setUp(self):
# make the random only semi-random with a fixed seed # make the random only semi-random with a fixed seed
random.seed(1) random.seed(1)
@ -283,9 +284,21 @@ class TestFormatGrid(TestCase):
def test_overlap(self): def test_overlap(self):
"""Grid with elements overlapping into the next slot""" """Grid with elements overlapping into the next slot"""
elements = ("alias", "batchcode", "batchcommands", "cmdsets", elements = (
"copy", "cpattr", "desc", "destroy", "dig", "alias",
"examine", "find", "force", "lock") "batchcode",
"batchcommands",
"cmdsets",
"copy",
"cpattr",
"desc",
"destroy",
"dig",
"examine",
"find",
"force",
"lock",
)
rows = utils.format_grid(elements, width=78) rows = utils.format_grid(elements, width=78)
self.assertEqual(len(rows), 2) self.assertEqual(len(rows), 2)
for element in elements: for element in elements:

View file

@ -1748,7 +1748,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
# debugger.Debugger().set_trace() # debugger.Debugger().set_trace()
# get the nth percentile as a good representation of average width # get the nth percentile as a good representation of average width
averlen = int(percentile(sorted(wls_percentile), 0.9)) + 2 # include extra space averlen = int(percentile(sorted(wls_percentile), 0.9)) + 2 # include extra space
aver_per_row = width // averlen + 1 aver_per_row = width // averlen + 1
if aver_per_row == 1: if aver_per_row == 1:

View file

@ -33,6 +33,7 @@ class TagTypeFilter(CharFilter):
""" """
This class lets you create different filters for tags of a specified db_tagtype. This class lets you create different filters for tags of a specified db_tagtype.
""" """
tag_type = None tag_type = None
def filter(self, qs, value): def filter(self, qs, value):
@ -45,11 +46,13 @@ class TagTypeFilter(CharFilter):
class AliasFilter(TagTypeFilter): class AliasFilter(TagTypeFilter):
"""A filter for objects by their aliases (tags with a tagtype of 'alias'""" """A filter for objects by their aliases (tags with a tagtype of 'alias'"""
tag_type = "alias" tag_type = "alias"
class PermissionFilter(TagTypeFilter): class PermissionFilter(TagTypeFilter):
"""A filter for objects by their permissions (tags with a tagtype of 'permission'""" """A filter for objects by their permissions (tags with a tagtype of 'permission'"""
tag_type = "permission" tag_type = "permission"
@ -58,6 +61,7 @@ SHARED_FIELDS = ["db_key", "db_typeclass_path", "db_tags__db_key", "db_tags__db_
class BaseTypeclassFilterSet(FilterSet): class BaseTypeclassFilterSet(FilterSet):
"""A parent class with filters for aliases and permissions""" """A parent class with filters for aliases and permissions"""
alias = AliasFilter(lookup_expr="iexact") alias = AliasFilter(lookup_expr="iexact")
permission = PermissionFilter(lookup_expr="iexact") permission = PermissionFilter(lookup_expr="iexact")
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key") name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key")
@ -81,14 +85,20 @@ class BaseTypeclassFilterSet(FilterSet):
class ObjectDBFilterSet(BaseTypeclassFilterSet): class ObjectDBFilterSet(BaseTypeclassFilterSet):
"""This adds filters for ObjectDB instances - characters, rooms, exits, etc""" """This adds filters for ObjectDB instances - characters, rooms, exits, etc"""
class Meta: class Meta:
model = ObjectDB model = ObjectDB
fields = SHARED_FIELDS + ["db_location__db_key", "db_home__db_key", "db_location__id", fields = SHARED_FIELDS + [
"db_home__id"] "db_location__db_key",
"db_home__db_key",
"db_location__id",
"db_home__id",
]
class AccountDBFilterSet(BaseTypeclassFilterSet): class AccountDBFilterSet(BaseTypeclassFilterSet):
"""This adds filters for Account objects""" """This adds filters for Account objects"""
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="username") name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="username")
class Meta: class Meta:
@ -98,7 +108,16 @@ class AccountDBFilterSet(BaseTypeclassFilterSet):
class ScriptDBFilterSet(BaseTypeclassFilterSet): class ScriptDBFilterSet(BaseTypeclassFilterSet):
"""This adds filters for Script objects""" """This adds filters for Script objects"""
class Meta: class Meta:
model = ScriptDB model = ScriptDB
fields = SHARED_FIELDS + ["db_desc", "db_obj__db_key", "db_obj__id", "db_account__id", fields = SHARED_FIELDS + [
"db_account__username", "db_is_active", "db_persistent", "db_interval"] "db_desc",
"db_obj__db_key",
"db_obj__id",
"db_account__id",
"db_account__username",
"db_is_active",
"db_persistent",
"db_interval",
]

View file

@ -9,6 +9,7 @@ class EvenniaPermission(permissions.BasePermission):
Evennia's permission structure. Based on the action in a given Evennia's permission structure. Based on the action in a given
view, we'll check a corresponding Evennia access/lock check. view, we'll check a corresponding Evennia access/lock check.
""" """
# subclass this to change these permissions # subclass this to change these permissions
MINIMUM_LIST_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_LIST_PERMISSION", "builder") MINIMUM_LIST_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_LIST_PERMISSION", "builder")
MINIMUM_CREATE_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_CREATE_PERMISSION", "builder") MINIMUM_CREATE_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_CREATE_PERMISSION", "builder")

View file

@ -62,7 +62,15 @@ class TypeclassSerializerMixin(object):
not have them render PK-related fields. not have them render PK-related fields.
""" """
shared_fields = ["id", "db_key", "attributes", "db_typeclass_path", "aliases", "tags", "permissions"] shared_fields = [
"id",
"db_key",
"attributes",
"db_typeclass_path",
"aliases",
"tags",
"permissions",
]
@staticmethod @staticmethod
def get_tags(obj): def get_tags(obj):
@ -98,7 +106,9 @@ class TypeclassSerializerMixin(object):
Returns: Returns:
List of TagSerializer data List of TagSerializer data
""" """
return TagSerializer(obj.permissions.get(return_tagobj=True, return_list=True), many=True).data return TagSerializer(
obj.permissions.get(return_tagobj=True, return_list=True), many=True
).data
@staticmethod @staticmethod
def get_attributes(obj): def get_attributes(obj):
@ -136,7 +146,13 @@ class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = DefaultObject model = DefaultObject
fields = ["db_location", "db_home", "contents", "exits", "nicks"] + TypeclassSerializerMixin.shared_fields fields = [
"db_location",
"db_home",
"contents",
"exits",
"nicks",
] + TypeclassSerializerMixin.shared_fields
read_only_fields = ["id"] read_only_fields = ["id"]
@staticmethod @staticmethod
@ -168,6 +184,7 @@ class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer): class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"""This uses the DefaultAccount object to have access to the sessions property""" """This uses the DefaultAccount object to have access to the sessions property"""
attributes = serializers.SerializerMethodField() attributes = serializers.SerializerMethodField()
nicks = serializers.SerializerMethodField() nicks = serializers.SerializerMethodField()
db_key = serializers.CharField(required=False) db_key = serializers.CharField(required=False)
@ -202,6 +219,11 @@ class ScriptDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = ScriptDB model = ScriptDB
fields = ["db_interval", "db_persistent", "db_start_delay", fields = [
"db_is_active", "db_repeats"] + TypeclassSerializerMixin.shared_fields "db_interval",
"db_persistent",
"db_start_delay",
"db_is_active",
"db_repeats",
] + TypeclassSerializerMixin.shared_fields
read_only_fields = ["id"] read_only_fields = ["id"]

View file

@ -14,9 +14,7 @@ urlpatterns = [
] ]
@override_settings( @override_settings(REST_API_ENABLED=True, ROOT_URLCONF=__name__, AUTH_USERNAME_VALIDATORS=[])
REST_API_ENABLED=True, ROOT_URLCONF=__name__, AUTH_USERNAME_VALIDATORS=[]
)
class TestEvenniaRESTApi(EvenniaTest): class TestEvenniaRESTApi(EvenniaTest):
client_class = APIClient client_class = APIClient
maxDiff = None maxDiff = None
@ -37,27 +35,58 @@ class TestEvenniaRESTApi(EvenniaTest):
def get_view_details(self, action): def get_view_details(self, action):
"""Helper function for generating list of named tuples""" """Helper function for generating list of named tuples"""
View = namedtuple("View", ["view_name", "obj", "list", "serializer", "create_data", "retrieve_data"]) View = namedtuple(
"View", ["view_name", "obj", "list", "serializer", "create_data", "retrieve_data"]
)
views = [ views = [
View("object-%s" % action, self.obj1, [self.obj1, self.char1, self.exit, self.room1, self.room2, self.obj2, View(
self.char2], serializers.ObjectDBSerializer, "object-%s" % action,
{"db_key": "object-create-test-name"}, self.obj1,
serializers.ObjectDBSerializer(self.obj1).data), [self.obj1, self.char1, self.exit, self.room1, self.room2, self.obj2, self.char2],
View("character-%s" % action, self.char1, [self.char1, self.char2], serializers.ObjectDBSerializer, serializers.ObjectDBSerializer,
{"db_key": "character-create-test-name"}, {"db_key": "object-create-test-name"},
serializers.ObjectDBSerializer(self.char1).data), serializers.ObjectDBSerializer(self.obj1).data,
View("exit-%s" % action, self.exit, [self.exit], serializers.ObjectDBSerializer, ),
{"db_key": "exit-create-test-name"}, View(
serializers.ObjectDBSerializer(self.exit).data), "character-%s" % action,
View("room-%s" % action, self.room1, [self.room1, self.room2], serializers.ObjectDBSerializer, self.char1,
{"db_key": "room-create-test-name"}, [self.char1, self.char2],
serializers.ObjectDBSerializer(self.room1).data), serializers.ObjectDBSerializer,
View("script-%s" % action, self.script, [self.script], serializers.ScriptDBSerializer, {"db_key": "character-create-test-name"},
{"db_key": "script-create-test-name"}, serializers.ObjectDBSerializer(self.char1).data,
serializers.ScriptDBSerializer(self.script).data), ),
View("account-%s" % action, self.account2, [self.account, self.account2], serializers.AccountSerializer, View(
{"username": "account-create-test-name"}, "exit-%s" % action,
serializers.AccountSerializer(self.account2).data), self.exit,
[self.exit],
serializers.ObjectDBSerializer,
{"db_key": "exit-create-test-name"},
serializers.ObjectDBSerializer(self.exit).data,
),
View(
"room-%s" % action,
self.room1,
[self.room1, self.room2],
serializers.ObjectDBSerializer,
{"db_key": "room-create-test-name"},
serializers.ObjectDBSerializer(self.room1).data,
),
View(
"script-%s" % action,
self.script,
[self.script],
serializers.ScriptDBSerializer,
{"db_key": "script-create-test-name"},
serializers.ScriptDBSerializer(self.script).data,
),
View(
"account-%s" % action,
self.account2,
[self.account, self.account2],
serializers.AccountSerializer,
{"username": "account-create-test-name"},
serializers.AccountSerializer(self.account2).data,
),
] ]
return views return views
@ -65,9 +94,7 @@ class TestEvenniaRESTApi(EvenniaTest):
views = self.get_view_details("detail") views = self.get_view_details("detail")
for view in views: for view in views:
with self.subTest(msg="Testing {} retrieve".format(view.view_name)): with self.subTest(msg="Testing {} retrieve".format(view.view_name)):
view_url = reverse( view_url = reverse("api:{}".format(view.view_name), kwargs={"pk": view.obj.pk})
"api:{}".format(view.view_name), kwargs={"pk": view.obj.pk}
)
response = self.client.get(view_url) response = self.client.get(view_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.data, view.retrieve_data) self.assertDictEqual(response.data, view.retrieve_data)
@ -76,9 +103,7 @@ class TestEvenniaRESTApi(EvenniaTest):
views = self.get_view_details("detail") views = self.get_view_details("detail")
for view in views: for view in views:
with self.subTest(msg="Testing {} update".format(view.view_name)): with self.subTest(msg="Testing {} update".format(view.view_name)):
view_url = reverse( view_url = reverse("api:{}".format(view.view_name), kwargs={"pk": view.obj.pk})
"api:{}".format(view.view_name), kwargs={"pk": view.obj.pk}
)
# test both PUT (update) and PATCH (partial update) here # test both PUT (update) and PATCH (partial update) here
for new_key, method in (("foobar", "put"), ("fizzbuzz", "patch")): for new_key, method in (("foobar", "put"), ("fizzbuzz", "patch")):
field = "username" if "account" in view.view_name else "db_key" field = "username" if "account" in view.view_name else "db_key"
@ -93,9 +118,7 @@ class TestEvenniaRESTApi(EvenniaTest):
views = self.get_view_details("detail") views = self.get_view_details("detail")
for view in views: for view in views:
with self.subTest(msg="Testing {} delete".format(view.view_name)): with self.subTest(msg="Testing {} delete".format(view.view_name)):
view_url = reverse( view_url = reverse("api:{}".format(view.view_name), kwargs={"pk": view.obj.pk})
"api:{}".format(view.view_name), kwargs={"pk": view.obj.pk}
)
response = self.client.delete(view_url) response = self.client.delete(view_url)
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
with self.assertRaises(ObjectDoesNotExist): with self.assertRaises(ObjectDoesNotExist):
@ -108,7 +131,9 @@ class TestEvenniaRESTApi(EvenniaTest):
view_url = reverse(f"api:{view.view_name}") view_url = reverse(f"api:{view.view_name}")
response = self.client.get(view_url) response = self.client.get(view_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertCountEqual(response.data['results'], [view.serializer(obj).data for obj in view.list]) self.assertCountEqual(
response.data["results"], [view.serializer(obj).data for obj in view.list]
)
def test_create(self): def test_create(self):
views = self.get_view_details("list") views = self.get_view_details("list")

View file

@ -22,18 +22,18 @@ from evennia.web.api.views import (
CharacterViewSet, CharacterViewSet,
ExitViewSet, ExitViewSet,
RoomViewSet, RoomViewSet,
ScriptDBViewSet ScriptDBViewSet,
) )
app_name = "api" app_name = "api"
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.trailing_slash = "/?" router.trailing_slash = "/?"
router.register(r'accounts', AccountDBViewSet, basename="account") router.register(r"accounts", AccountDBViewSet, basename="account")
router.register(r'objects', ObjectDBViewSet, basename="object") router.register(r"objects", ObjectDBViewSet, basename="object")
router.register(r'characters', CharacterViewSet, basename="character") router.register(r"characters", CharacterViewSet, basename="character")
router.register(r'exits', ExitViewSet, basename="exit") router.register(r"exits", ExitViewSet, basename="exit")
router.register(r'rooms', RoomViewSet, basename="room") router.register(r"rooms", RoomViewSet, basename="room")
router.register(r'scripts', ScriptDBViewSet, basename="script") router.register(r"scripts", ScriptDBViewSet, basename="script")
urlpatterns = router.urls urlpatterns = router.urls

View file

@ -14,7 +14,12 @@ from evennia.objects.models import ObjectDB
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultRoom from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultRoom
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.scripts.models import ScriptDB from evennia.scripts.models import ScriptDB
from evennia.web.api.serializers import ObjectDBSerializer, AccountSerializer, ScriptDBSerializer, AttributeSerializer from evennia.web.api.serializers import (
ObjectDBSerializer,
AccountSerializer,
ScriptDBSerializer,
AttributeSerializer,
)
from evennia.web.api.filters import ObjectDBFilterSet, AccountDBFilterSet, ScriptDBFilterSet from evennia.web.api.filters import ObjectDBFilterSet, AccountDBFilterSet, ScriptDBFilterSet
from evennia.web.api.permissions import EvenniaPermission from evennia.web.api.permissions import EvenniaPermission
@ -24,6 +29,7 @@ class TypeclassViewSetMixin(object):
This mixin adds some shared functionality to each viewset of a typeclass. They all use the same This mixin adds some shared functionality to each viewset of a typeclass. They all use the same
permission classes and filter backend. You can override any of these in your own viewsets. permission classes and filter backend. You can override any of these in your own viewsets.
""" """
# permission classes determine who is authorized to call the view # permission classes determine who is authorized to call the view
permission_classes = [EvenniaPermission] permission_classes = [EvenniaPermission]
# the filter backend allows for retrieval views to have filter arguments passed to it, # the filter backend allows for retrieval views to have filter arguments passed to it,
@ -58,7 +64,10 @@ class TypeclassViewSetMixin(object):
handler.add(key=key, value=value, category=category) handler.add(key=key, value=value, category=category)
else: else:
handler.remove(key=key, category=category) handler.remove(key=key, category=category)
return Response(AttributeSerializer(obj.db_attributes.all(), many=True).data, status=status.HTTP_200_OK) return Response(
AttributeSerializer(obj.db_attributes.all(), many=True).data,
status=status.HTTP_200_OK,
)
return Response(attr.errors, status=status.HTTP_400_BAD_REQUEST) return Response(attr.errors, status=status.HTTP_400_BAD_REQUEST)
@ -69,6 +78,7 @@ class ObjectDBViewSet(TypeclassViewSetMixin, ModelViewSet):
instances. Serializers are similar to django forms, used for the instances. Serializers are similar to django forms, used for the
transmitting of data (typically json). transmitting of data (typically json).
""" """
serializer_class = ObjectDBSerializer serializer_class = ObjectDBSerializer
queryset = ObjectDB.objects.all() queryset = ObjectDB.objects.all()
filterset_class = ObjectDBFilterSet filterset_class = ObjectDBFilterSet
@ -79,21 +89,27 @@ class CharacterViewSet(ObjectDBViewSet):
This overrides the queryset to only retrieve Character objects This overrides the queryset to only retrieve Character objects
based on your DefaultCharacter typeclass path. based on your DefaultCharacter typeclass path.
""" """
queryset = DefaultCharacter.objects.typeclass_search(DefaultCharacter.path, include_children=True)
queryset = DefaultCharacter.objects.typeclass_search(
DefaultCharacter.path, include_children=True
)
class RoomViewSet(ObjectDBViewSet): class RoomViewSet(ObjectDBViewSet):
"""Viewset for Room objects""" """Viewset for Room objects"""
queryset = DefaultRoom.objects.typeclass_search(DefaultRoom.path, include_children=True) queryset = DefaultRoom.objects.typeclass_search(DefaultRoom.path, include_children=True)
class ExitViewSet(ObjectDBViewSet): class ExitViewSet(ObjectDBViewSet):
"""Viewset for Exit objects""" """Viewset for Exit objects"""
queryset = DefaultExit.objects.typeclass_search(DefaultExit.path, include_children=True) queryset = DefaultExit.objects.typeclass_search(DefaultExit.path, include_children=True)
class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet): class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
"""Viewset for Account objects""" """Viewset for Account objects"""
serializer_class = AccountSerializer serializer_class = AccountSerializer
queryset = AccountDB.objects.all() queryset = AccountDB.objects.all()
filterset_class = AccountDBFilterSet filterset_class = AccountDBFilterSet
@ -101,6 +117,7 @@ class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet): class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet):
"""Viewset for Script objects""" """Viewset for Script objects"""
serializer_class = ScriptDBSerializer serializer_class = ScriptDBSerializer
queryset = ScriptDB.objects.all() queryset = ScriptDB.objects.all()
filterset_class = ScriptDBFilterSet filterset_class = ScriptDBFilterSet

View file

@ -24,4 +24,4 @@ urlpatterns = [
] ]
if settings.REST_API_ENABLED: if settings.REST_API_ENABLED:
urlpatterns += [url(r'^api/', include("evennia.web.api.urls", namespace="api"))] urlpatterns += [url(r"^api/", include("evennia.web.api.urls", namespace="api"))]