kopia lustrzana https://gitlab.com/jaywink/federation
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
rodzic
62380a5d77
commit
a2920444ca
|
@ -1,10 +1,36 @@
|
|||
# -*- 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):
|
||||
"""Diaspora comments additionally have an author_signature."""
|
||||
@property
|
||||
def author_signature(self):
|
||||
#TODO: implement at later stage when outbound payloads are to be used
|
||||
return ""
|
||||
author_signature = ""
|
||||
|
||||
def to_xml(self):
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -3,13 +3,14 @@ from datetime import datetime
|
|||
|
||||
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 = {
|
||||
"status_message": Post,
|
||||
"status_message": DiasporaPost,
|
||||
"photo": Image,
|
||||
"comment": Comment,
|
||||
"comment": DiasporaComment,
|
||||
}
|
||||
|
||||
BOOLEAN_KEYS = [
|
||||
|
|
|
@ -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
|
|
@ -10,7 +10,6 @@ from Crypto.Random import get_random_bytes
|
|||
from Crypto.Signature import PKCS1_v1_5 as PKCSSign
|
||||
from lxml import etree
|
||||
|
||||
from federation.entities.diaspora.generators import EntityConverter
|
||||
from federation.exceptions import EncryptedMessageError, NoHeaderInMessageError, NoSenderKeyFoundError
|
||||
from federation.protocols.base import BaseProtocol
|
||||
|
||||
|
@ -164,8 +163,7 @@ class Protocol(BaseProtocol):
|
|||
|
||||
def build_send(self, from_user, to_user, entity, *args, **kwargs):
|
||||
"""Build POST data for sending out to remotes."""
|
||||
converter = EntityConverter(entity)
|
||||
xml = converter.convert_to_xml()
|
||||
xml = entity.to_xml()
|
||||
self.init_message(xml, from_user.handle, from_user.private_key)
|
||||
xml = quote_plus(
|
||||
self.create_salmon_envelope(to_user.key))
|
||||
|
|
|
@ -1,28 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from unittest.mock import patch
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from federation.entities.base import Post
|
||||
from federation.entities.diaspora.entities import DiasporaComment
|
||||
from federation.entities.diaspora.generators import EntityConverter
|
||||
from federation.entities.diaspora.entities import DiasporaComment, DiasporaPost
|
||||
|
||||
|
||||
class TestEntityConverterCallsToXML(object):
|
||||
|
||||
def test_entity_converter_call_to_xml(self):
|
||||
entity = Post()
|
||||
|
||||
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()
|
||||
class TestEntitiesConvertToXML(object):
|
||||
def test_post_to_xml(self):
|
||||
entity = DiasporaPost(raw_content="raw_content", guid="guid", handle="handle", public=True)
|
||||
result = entity.to_xml()
|
||||
assert result.tag == "status_message"
|
||||
assert len(result.find("created_at").text) > 0
|
||||
result.find("created_at").text = "" # timestamp makes testing painful
|
||||
|
@ -31,10 +16,9 @@ class TestEntityConverterCallsToXML(object):
|
|||
b"</created_at></status_message>"
|
||||
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_converter = EntityConverter(entity)
|
||||
result = entity_converter.convert_to_xml()
|
||||
result = entity.to_xml()
|
||||
assert result.tag == "comment"
|
||||
converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
|
||||
b"<author_signature></author_signature><text>raw_content</text>" \
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
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.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT
|
||||
|
||||
|
@ -12,6 +13,7 @@ class TestDiasporaEntityMappersReceive(object):
|
|||
entities = message_to_objects(DIASPORA_POST_SIMPLE)
|
||||
assert len(entities) == 1
|
||||
post = entities[0]
|
||||
assert isinstance(post, DiasporaPost)
|
||||
assert isinstance(post, Post)
|
||||
assert post.raw_content == "((status message))"
|
||||
assert post.guid == "((guid))"
|
||||
|
@ -23,6 +25,7 @@ class TestDiasporaEntityMappersReceive(object):
|
|||
entities = message_to_objects(DIASPORA_POST_COMMENT)
|
||||
assert len(entities) == 1
|
||||
comment = entities[0]
|
||||
assert isinstance(comment, DiasporaComment)
|
||||
assert isinstance(comment, Comment)
|
||||
assert comment.target_guid == "((parent_guid))"
|
||||
assert comment.guid == "((guid))"
|
||||
|
|
|
@ -4,7 +4,7 @@ from Crypto.PublicKey import RSA
|
|||
import pytest
|
||||
|
||||
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.protocols.diaspora.protocol import Protocol
|
||||
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):
|
||||
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
|
||||
to_user = Mock(key=RSA.generate(2048).publickey())
|
||||
entity = Post()
|
||||
entity = DiasporaPost()
|
||||
data = handle_create_payload(from_user, to_user, entity)
|
||||
assert len(data) > 0
|
||||
parts = data.split("=")
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[pytest]
|
||||
testpaths = federation
|
Ładowanie…
Reference in New Issue