Refactor: move diaspora XML generators to entities themselves

This change is backwards incompatible. Any implementations wishing to create a valid Post for example for diaspora shouls now use DiasporaPost.
merge-requests/130/head
Jason Robinson 2016-04-03 22:54:32 +03:00
rodzic 62380a5d77
commit a2920444ca
9 zmienionych plików z 85 dodań i 104 usunięć

Wyświetl plik

@ -1,10 +1,36 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from ..base import Comment from lxml import etree
from federation.entities.base import Comment, Post
from federation.entities.diaspora.utils import format_dt, struct_to_xml
class DiasporaComment(Comment): class DiasporaComment(Comment):
"""Diaspora comments additionally have an author_signature.""" """Diaspora comments additionally have an author_signature."""
@property author_signature = ""
def author_signature(self):
#TODO: implement at later stage when outbound payloads are to be used def to_xml(self):
return "" element = etree.Element("comment")
struct_to_xml(element, [
{'guid': self.guid},
{'parent_guid': self.target_guid},
{'author_signature': self.author_signature},
{'text': self.raw_content},
{'diaspora_handle': self.handle},
])
return element
class DiasporaPost(Post):
"""Diaspora post, ie status message."""
def to_xml(self):
"""Convert to XML message."""
element = etree.Element("status_message")
struct_to_xml(element, [
{'raw_message': self.raw_content},
{'guid': self.guid},
{'diaspora_handle': self.handle},
{'public': 'true' if self.public else 'false'},
{'created_at': format_dt(self.created_at)}
])
return element

Wyświetl plik

@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
from dateutil.tz import tzlocal, tzutc
from lxml import etree
def ensure_timezone(dt, tz=None):
"""
Make sure the datetime <dt> has a timezone set, using timezone <tz> if it
doesn't. <tz> defaults to the local timezone.
"""
if dt.tzinfo is None:
return dt.replace(tzinfo=tz or tzlocal())
else:
return dt
class EntityConverter(object):
def __init__(self, entity):
self.entity = entity
self.entity_type = entity.__class__.__name__.lower()
def struct_to_xml(self, node, struct):
"""
Turn a list of dicts into XML nodes with tag names taken from the dict
keys and element text taken from dict values. This is a list of dicts
so that the XML nodes can be ordered in the XML output.
"""
for obj in struct:
for k, v in obj.items():
etree.SubElement(node, k).text = v
def convert_to_xml(self):
#TODO: move these to entities themselves as `to_xml` methods
if hasattr(self, "%s_to_xml" % self.entity_type):
method_name = "%s_to_xml" % self.entity_type
return getattr(self, method_name)()
def format_dt(cls, dt):
"""
Format a datetime in the way that D* nodes expect.
"""
return ensure_timezone(dt).astimezone(tzutc()).strftime(
'%Y-%m-%d %H:%M:%S %Z'
)
def post_to_xml(self):
req = etree.Element("status_message")
self.struct_to_xml(req, [
{'raw_message': self.entity.raw_content},
{'guid': self.entity.guid},
{'diaspora_handle': self.entity.handle},
{'public': 'true' if self.entity.public else 'false'},
{'created_at': self.format_dt(self.entity.created_at)}
])
return req
def diasporacomment_to_xml(self):
req = etree.Element("comment")
self.struct_to_xml(req, [
{'guid': self.entity.guid},
{'parent_guid': self.entity.target_guid},
{'author_signature': self.entity.author_signature},
{'text': self.entity.raw_content},
{'diaspora_handle': self.entity.handle},
])
return req

Wyświetl plik

@ -3,13 +3,14 @@ from datetime import datetime
from lxml import etree from lxml import etree
from federation.entities.base import Post, Image, Comment from federation.entities.base import Image
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment
MAPPINGS = { MAPPINGS = {
"status_message": Post, "status_message": DiasporaPost,
"photo": Image, "photo": Image,
"comment": Comment, "comment": DiasporaComment,
} }
BOOLEAN_KEYS = [ BOOLEAN_KEYS = [

Wyświetl plik

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from dateutil.tz import tzlocal, tzutc
from lxml import etree
def ensure_timezone(dt, tz=None):
"""
Make sure the datetime <dt> has a timezone set, using timezone <tz> if it
doesn't. <tz> defaults to the local timezone.
"""
if dt.tzinfo is None:
return dt.replace(tzinfo=tz or tzlocal())
else:
return dt
def format_dt(dt):
"""
Format a datetime in the way that D* nodes expect.
"""
return ensure_timezone(dt).astimezone(tzutc()).strftime(
'%Y-%m-%d %H:%M:%S %Z'
)
def struct_to_xml(node, struct):
"""
Turn a list of dicts into XML nodes with tag names taken from the dict
keys and element text taken from dict values. This is a list of dicts
so that the XML nodes can be ordered in the XML output.
"""
for obj in struct:
for k, v in obj.items():
etree.SubElement(node, k).text = v

Wyświetl plik

@ -10,7 +10,6 @@ from Crypto.Random import get_random_bytes
from Crypto.Signature import PKCS1_v1_5 as PKCSSign from Crypto.Signature import PKCS1_v1_5 as PKCSSign
from lxml import etree from lxml import etree
from federation.entities.diaspora.generators import EntityConverter
from federation.exceptions import EncryptedMessageError, NoHeaderInMessageError, NoSenderKeyFoundError from federation.exceptions import EncryptedMessageError, NoHeaderInMessageError, NoSenderKeyFoundError
from federation.protocols.base import BaseProtocol from federation.protocols.base import BaseProtocol
@ -164,8 +163,7 @@ class Protocol(BaseProtocol):
def build_send(self, from_user, to_user, entity, *args, **kwargs): def build_send(self, from_user, to_user, entity, *args, **kwargs):
"""Build POST data for sending out to remotes.""" """Build POST data for sending out to remotes."""
converter = EntityConverter(entity) xml = entity.to_xml()
xml = converter.convert_to_xml()
self.init_message(xml, from_user.handle, from_user.private_key) self.init_message(xml, from_user.handle, from_user.private_key)
xml = quote_plus( xml = quote_plus(
self.create_salmon_envelope(to_user.key)) self.create_salmon_envelope(to_user.key))

Wyświetl plik

@ -1,28 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from unittest.mock import patch
from lxml import etree from lxml import etree
from federation.entities.base import Post from federation.entities.diaspora.entities import DiasporaComment, DiasporaPost
from federation.entities.diaspora.entities import DiasporaComment
from federation.entities.diaspora.generators import EntityConverter
class TestEntityConverterCallsToXML(object): class TestEntitiesConvertToXML(object):
def test_post_to_xml(self):
def test_entity_converter_call_to_xml(self): entity = DiasporaPost(raw_content="raw_content", guid="guid", handle="handle", public=True)
entity = Post() result = entity.to_xml()
with patch.object(EntityConverter, "post_to_xml", return_value="foo") as mock_to_xml:
entity_converter = EntityConverter(entity=entity)
result = entity_converter.convert_to_xml()
assert result == "foo"
assert mock_to_xml.called
def test_entity_converter_converts_a_post(self):
entity = Post(raw_content="raw_content", guid="guid", handle="handle", public=True)
entity_converter = EntityConverter(entity)
result = entity_converter.convert_to_xml()
assert result.tag == "status_message" assert result.tag == "status_message"
assert len(result.find("created_at").text) > 0 assert len(result.find("created_at").text) > 0
result.find("created_at").text = "" # timestamp makes testing painful result.find("created_at").text = "" # timestamp makes testing painful
@ -31,10 +16,9 @@ class TestEntityConverterCallsToXML(object):
b"</created_at></status_message>" b"</created_at></status_message>"
assert etree.tostring(result) == converted assert etree.tostring(result) == converted
def test_entity_converter_converts_a_comment(self): def test_comment_to_xml(self):
entity = DiasporaComment(raw_content="raw_content", guid="guid", target_guid="target_guid", handle="handle") entity = DiasporaComment(raw_content="raw_content", guid="guid", target_guid="target_guid", handle="handle")
entity_converter = EntityConverter(entity) result = entity.to_xml()
result = entity_converter.convert_to_xml()
assert result.tag == "comment" assert result.tag == "comment"
converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \ converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
b"<author_signature></author_signature><text>raw_content</text>" \ b"<author_signature></author_signature><text>raw_content</text>" \

Wyświetl plik

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from datetime import datetime from datetime import datetime
from federation.entities.base import Post, Comment from federation.entities.base import Comment, Post
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment
from federation.entities.diaspora.mappers import message_to_objects from federation.entities.diaspora.mappers import message_to_objects
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT
@ -12,6 +13,7 @@ class TestDiasporaEntityMappersReceive(object):
entities = message_to_objects(DIASPORA_POST_SIMPLE) entities = message_to_objects(DIASPORA_POST_SIMPLE)
assert len(entities) == 1 assert len(entities) == 1
post = entities[0] post = entities[0]
assert isinstance(post, DiasporaPost)
assert isinstance(post, Post) assert isinstance(post, Post)
assert post.raw_content == "((status message))" assert post.raw_content == "((status message))"
assert post.guid == "((guid))" assert post.guid == "((guid))"
@ -23,6 +25,7 @@ class TestDiasporaEntityMappersReceive(object):
entities = message_to_objects(DIASPORA_POST_COMMENT) entities = message_to_objects(DIASPORA_POST_COMMENT)
assert len(entities) == 1 assert len(entities) == 1
comment = entities[0] comment = entities[0]
assert isinstance(comment, DiasporaComment)
assert isinstance(comment, Comment) assert isinstance(comment, Comment)
assert comment.target_guid == "((parent_guid))" assert comment.target_guid == "((parent_guid))"
assert comment.guid == "((guid))" assert comment.guid == "((guid))"

Wyświetl plik

@ -4,7 +4,7 @@ from Crypto.PublicKey import RSA
import pytest import pytest
from federation.controllers import handle_receive, handle_create_payload from federation.controllers import handle_receive, handle_create_payload
from federation.entities.base import Post from federation.entities.diaspora.entities import DiasporaPost
from federation.exceptions import NoSuitableProtocolFoundError from federation.exceptions import NoSuitableProtocolFoundError
from federation.protocols.diaspora.protocol import Protocol from federation.protocols.diaspora.protocol import Protocol
from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
@ -35,7 +35,7 @@ class TestHandleCreatePayloadBuildsAPayload(object):
def test_handle_create_payload_builds_an_xml(self): def test_handle_create_payload_builds_an_xml(self):
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld") from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
to_user = Mock(key=RSA.generate(2048).publickey()) to_user = Mock(key=RSA.generate(2048).publickey())
entity = Post() entity = DiasporaPost()
data = handle_create_payload(from_user, to_user, entity) data = handle_create_payload(from_user, to_user, entity)
assert len(data) > 0 assert len(data) > 0
parts = data.split("=") parts = data.split("=")

2
pytest.ini 100644
Wyświetl plik

@ -0,0 +1,2 @@
[pytest]
testpaths = federation