kopia lustrzana https://gitlab.com/jaywink/federation
rodzic
08e0abf180
commit
3c27abf0a9
|
@ -1,7 +1,8 @@
|
|||
## [unreleased]
|
||||
|
||||
## Added
|
||||
- Relationship base entity which represents relationships between two handles. Types can be following, sharing, ignoring and blocking. The Diaspora counterpart, DiasporaRequest, which represents a sharing/following request is outwards a single entity, but incoming a double entity, handled by creating both a sharing and following version of the relationship.
|
||||
- `Relationship` base entity which represents relationships between two handles. Types can be following, sharing, ignoring and blocking. The Diaspora counterpart, `DiasporaRequest`, which represents a sharing/following request is outwards a single entity, but incoming a double entity, handled by creating both a sharing and following version of the relationship.
|
||||
- `Profile` base entity and Diaspora counterpart `DiasporaProfile`. Represents a user profile.
|
||||
|
||||
## Changed
|
||||
- Unlock most of the direct dependencies to a certain version range. Unlock all of test requirements to any version.
|
||||
|
|
|
@ -11,6 +11,7 @@ class BaseEntity(object):
|
|||
_required = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._required = []
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
@ -32,7 +33,7 @@ class BaseEntity(object):
|
|||
required_fulfilled = set(self._required).issubset(set(attributes))
|
||||
if not required_fulfilled:
|
||||
raise ValueError(
|
||||
"Not all required attributes fulfilled. Required: {required}".format(required=self._required)
|
||||
"Not all required attributes fulfilled. Required: {required}".format(required=set(self._required))
|
||||
)
|
||||
|
||||
|
||||
|
@ -44,7 +45,7 @@ class GUIDMixin(BaseEntity):
|
|||
self._required += ["guid"]
|
||||
|
||||
def validate_guid(self):
|
||||
if len(self.guid) < 16:
|
||||
if self.guid and len(self.guid) < 16:
|
||||
raise ValueError("GUID must be at least 16 characters")
|
||||
|
||||
|
||||
|
@ -157,7 +158,7 @@ class Reaction(GUIDMixin, ParticipationMixin, CreatedAtMixin, HandleMixin):
|
|||
|
||||
|
||||
class Relationship(CreatedAtMixin, HandleMixin):
|
||||
"""Represents a """
|
||||
"""Represents a relationship between two handles."""
|
||||
target_handle = ""
|
||||
relationship = ""
|
||||
|
||||
|
@ -178,3 +179,28 @@ class Relationship(CreatedAtMixin, HandleMixin):
|
|||
raise ValueError("relationship should be one of: {valid}".format(
|
||||
valid=", ".join(self._relationship_valid_values)
|
||||
))
|
||||
|
||||
|
||||
class Profile(CreatedAtMixin, HandleMixin, RawContentMixin, PublicMixin, GUIDMixin):
|
||||
"""Represents a profile for a user."""
|
||||
name = ""
|
||||
email = ""
|
||||
image_urls = {
|
||||
"small": "", "medium": "", "large": ""
|
||||
}
|
||||
gender = ""
|
||||
location = ""
|
||||
nsfw = False
|
||||
tag_list = []
|
||||
public_key = ""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Don't require a guid for Profile
|
||||
self._required.remove("guid")
|
||||
|
||||
def validate_email(self):
|
||||
if self.email:
|
||||
validator = Email()
|
||||
if not validator.is_valid(self.email):
|
||||
raise ValueError("Email is not valid")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from lxml import etree
|
||||
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship, Profile
|
||||
from federation.entities.diaspora.utils import format_dt, struct_to_xml, get_base_attributes
|
||||
|
||||
|
||||
|
@ -73,3 +73,26 @@ class DiasporaRequest(DiasporaEntityMixin, Relationship):
|
|||
{"recipient_handle": self.target_handle},
|
||||
])
|
||||
return element
|
||||
|
||||
|
||||
class DiasporaProfile(DiasporaEntityMixin, Profile):
|
||||
"""Diaspora profile."""
|
||||
|
||||
def to_xml(self):
|
||||
"""Convert to XML message."""
|
||||
element = etree.Element("profile")
|
||||
struct_to_xml(element, [
|
||||
{"diaspora_handle": self.handle},
|
||||
{"first_name": self.name},
|
||||
{"last_name": ""}, # Not used in Diaspora modern profiles
|
||||
{"image_url": self.image_urls["large"]},
|
||||
{"image_url_small": self.image_urls["small"]},
|
||||
{"image_url_medium": self.image_urls["medium"]},
|
||||
{"gender": self.gender},
|
||||
{"bio": self.raw_content},
|
||||
{"location": self.location},
|
||||
{"searchable": "true" if self.public else "false"},
|
||||
{"nsfw": "true" if self.nsfw else "false"},
|
||||
{"tag_string": " ".join(["#%s" % tag for tag in self.tag_list])},
|
||||
])
|
||||
return element
|
||||
|
|
|
@ -3,8 +3,9 @@ from datetime import datetime
|
|||
|
||||
from lxml import etree
|
||||
|
||||
from federation.entities.base import Image, Relationship, Post, Reaction, Comment
|
||||
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest
|
||||
from federation.entities.base import Image, Relationship, Post, Reaction, Comment, Profile
|
||||
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest, \
|
||||
DiasporaProfile
|
||||
|
||||
MAPPINGS = {
|
||||
"status_message": DiasporaPost,
|
||||
|
@ -12,10 +13,12 @@ MAPPINGS = {
|
|||
"comment": DiasporaComment,
|
||||
"like": DiasporaLike,
|
||||
"request": DiasporaRequest,
|
||||
"profile": DiasporaProfile,
|
||||
}
|
||||
|
||||
BOOLEAN_KEYS = [
|
||||
"public",
|
||||
"nsfw",
|
||||
]
|
||||
|
||||
DATETIME_KEYS = [
|
||||
|
@ -62,12 +65,32 @@ def transform_attributes(attrs):
|
|||
transformed["target_handle"] = value
|
||||
elif key == "parent_guid":
|
||||
transformed["target_guid"] = value
|
||||
elif key == "first_name":
|
||||
transformed["name"] = value
|
||||
elif key == "image_url":
|
||||
if "image_urls" not in transformed:
|
||||
transformed["image_urls"] = {}
|
||||
transformed["image_urls"]["large"] = value
|
||||
elif key == "image_url_small":
|
||||
if "image_urls" not in transformed:
|
||||
transformed["image_urls"] = {}
|
||||
transformed["image_urls"]["small"] = value
|
||||
elif key == "image_url_medium":
|
||||
if "image_urls" not in transformed:
|
||||
transformed["image_urls"] = {}
|
||||
transformed["image_urls"]["medium"] = value
|
||||
elif key == "tag_string":
|
||||
transformed["tag_list"] = value.replace("#", "").split(" ")
|
||||
elif key == "bio":
|
||||
transformed["raw_content"] = value
|
||||
elif key == "searchable":
|
||||
transformed["public"] = True if value == "true" else False
|
||||
elif key in BOOLEAN_KEYS:
|
||||
transformed[key] = True if value == "true" else False
|
||||
elif key in DATETIME_KEYS:
|
||||
transformed[key] = datetime.strptime(value, "%Y-%m-%d %H:%M:%S %Z")
|
||||
else:
|
||||
transformed[key] = value
|
||||
transformed[key] = value or ""
|
||||
return transformed
|
||||
|
||||
|
||||
|
@ -84,7 +107,7 @@ def get_outbound_entity(entity):
|
|||
An instance of the correct protocol specific entity.
|
||||
"""
|
||||
cls = entity.__class__
|
||||
if cls in [DiasporaPost, DiasporaRequest, DiasporaComment, DiasporaLike]:
|
||||
if cls in [DiasporaPost, DiasporaRequest, DiasporaComment, DiasporaLike, DiasporaProfile]:
|
||||
# Already fine
|
||||
return entity
|
||||
elif cls == Post:
|
||||
|
@ -98,4 +121,6 @@ def get_outbound_entity(entity):
|
|||
if entity.relationship in ["sharing", "following"]:
|
||||
# Unfortunately we must send out in both cases since in Diaspora they are the same thing
|
||||
return DiasporaRequest.from_base(entity)
|
||||
elif cls == Profile:
|
||||
return DiasporaProfile.from_base(entity)
|
||||
raise ValueError("Don't know how to convert this base entity to Diaspora protocol entities.")
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from lxml import etree
|
||||
|
||||
from federation.entities.diaspora.entities import DiasporaComment, DiasporaPost, DiasporaLike, DiasporaRequest
|
||||
from federation.entities.diaspora.entities import DiasporaComment, DiasporaPost, DiasporaLike, DiasporaRequest, \
|
||||
DiasporaProfile
|
||||
|
||||
|
||||
class TestEntitiesConvertToXML(object):
|
||||
|
@ -41,3 +42,19 @@ class TestEntitiesConvertToXML(object):
|
|||
converted = b"<request><sender_handle>bob@example.com</sender_handle>" \
|
||||
b"<recipient_handle>alice@example.com</recipient_handle></request>"
|
||||
assert etree.tostring(result) == converted
|
||||
|
||||
def test_profile_to_xml(self):
|
||||
entity = DiasporaProfile(
|
||||
handle="bob@example.com", raw_content="foobar", name="Bob Bobertson", public=True,
|
||||
tag_list=["socialfederation", "federation"], image_urls={
|
||||
"large": "urllarge", "medium": "urlmedium", "small": "urlsmall"
|
||||
}
|
||||
)
|
||||
result = entity.to_xml()
|
||||
assert result.tag == "profile"
|
||||
converted = b"<profile><diaspora_handle>bob@example.com</diaspora_handle>" \
|
||||
b"<first_name>Bob Bobertson</first_name><last_name></last_name><image_url>urllarge</image_url>" \
|
||||
b"<image_url_small>urlsmall</image_url_small><image_url_medium>urlmedium</image_url_medium>" \
|
||||
b"<gender></gender><bio>foobar</bio><location></location><searchable>true</searchable>" \
|
||||
b"<nsfw>false</nsfw><tag_string>#socialfederation #federation</tag_string></profile>"
|
||||
assert etree.tostring(result) == converted
|
||||
|
|
|
@ -3,11 +3,12 @@ from datetime import datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship
|
||||
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship, Profile
|
||||
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest, \
|
||||
DiasporaProfile
|
||||
from federation.entities.diaspora.mappers import message_to_objects, get_outbound_entity
|
||||
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE, \
|
||||
DIASPORA_REQUEST
|
||||
DIASPORA_REQUEST, DIASPORA_PROFILE
|
||||
|
||||
|
||||
class TestDiasporaEntityMappersReceive(object):
|
||||
|
@ -63,6 +64,24 @@ class TestDiasporaEntityMappersReceive(object):
|
|||
assert sharing.relationship == "sharing"
|
||||
assert following.relationship == "following"
|
||||
|
||||
def test_message_to_objects_profile(self):
|
||||
entities = message_to_objects(DIASPORA_PROFILE)
|
||||
assert len(entities) == 1
|
||||
profile = entities[0]
|
||||
assert profile.handle == "bob@example.com"
|
||||
assert profile.name == "Bob Bobertson"
|
||||
assert profile.image_urls == {
|
||||
"large": "https://example.com/uploads/images/thumb_large_c833747578b5.jpg",
|
||||
"medium": "https://example.com/uploads/images/thumb_medium_c8b1aab04f3.jpg",
|
||||
"small": "https://example.com/uploads/images/thumb_small_c8b147578b5.jpg",
|
||||
}
|
||||
assert profile.gender == ""
|
||||
assert profile.raw_content == "A cool bio"
|
||||
assert profile.location == "Helsinki"
|
||||
assert profile.public == True
|
||||
assert profile.nsfw == False
|
||||
assert profile.tag_list == ["socialfederation", "federation"]
|
||||
|
||||
|
||||
class TestGetOutboundEntity(object):
|
||||
def test_already_fine_entities_are_returned_as_is(self):
|
||||
|
@ -74,6 +93,8 @@ class TestGetOutboundEntity(object):
|
|||
assert get_outbound_entity(entity) == entity
|
||||
entity = DiasporaRequest()
|
||||
assert get_outbound_entity(entity) == entity
|
||||
entity = DiasporaProfile()
|
||||
assert get_outbound_entity(entity) == entity
|
||||
|
||||
def test_post_is_converted_to_diasporapost(self):
|
||||
entity = Post()
|
||||
|
@ -93,6 +114,10 @@ class TestGetOutboundEntity(object):
|
|||
entity = Relationship(relationship="following")
|
||||
assert isinstance(get_outbound_entity(entity), DiasporaRequest)
|
||||
|
||||
def test_profile_is_converted_to_diasporaprofile(self):
|
||||
entity = Profile()
|
||||
assert isinstance(get_outbound_entity(entity), DiasporaProfile)
|
||||
|
||||
def test_other_reaction_raises(self):
|
||||
entity = Reaction(reaction="foo")
|
||||
with pytest.raises(ValueError):
|
||||
|
|
|
@ -3,7 +3,7 @@ from unittest.mock import Mock
|
|||
|
||||
import pytest
|
||||
|
||||
from federation.entities.base import BaseEntity, Relationship
|
||||
from federation.entities.base import BaseEntity, Relationship, Profile
|
||||
from federation.tests.factories.entities import TaggedPostFactory, PostFactory
|
||||
|
||||
|
||||
|
@ -47,3 +47,18 @@ class TestRelationshipEntity(object):
|
|||
with pytest.raises(ValueError):
|
||||
entity = Relationship(handle="bob@example.com", target_handle="fefle.com", relationship="following")
|
||||
entity.validate()
|
||||
|
||||
|
||||
class TestProfileEntity(object):
|
||||
def test_instance_creation(self):
|
||||
entity = Profile(handle="bob@example.com", raw_content="foobar")
|
||||
assert entity
|
||||
|
||||
def test_instance_creation_validates_email_value(self):
|
||||
with pytest.raises(ValueError):
|
||||
entity = Profile(handle="bob@example.com", raw_content="foobar", email="foobar")
|
||||
entity.validate()
|
||||
|
||||
def test_guid_is_not_mandatory(self):
|
||||
entity = Profile(handle="bob@example.com", raw_content="foobar")
|
||||
entity.validate()
|
||||
|
|
|
@ -77,3 +77,23 @@ DIASPORA_REQUEST = """<XML>
|
|||
</post>
|
||||
</XML>
|
||||
"""
|
||||
|
||||
DIASPORA_PROFILE = """<XML>
|
||||
<post>
|
||||
<profile>
|
||||
<diaspora_handle>bob@example.com</diaspora_handle>
|
||||
<first_name>Bob Bobertson</first_name>
|
||||
<last_name></last_name>
|
||||
<image_url>https://example.com/uploads/images/thumb_large_c833747578b5.jpg</image_url>
|
||||
<image_url_small>https://example.com/uploads/images/thumb_small_c8b147578b5.jpg</image_url_small>
|
||||
<image_url_medium>https://example.com/uploads/images/thumb_medium_c8b1aab04f3.jpg</image_url_medium>
|
||||
<gender></gender>
|
||||
<bio>A cool bio</bio>
|
||||
<location>Helsinki</location>
|
||||
<searchable>true</searchable>
|
||||
<nsfw>false</nsfw>
|
||||
<tag_string>#socialfederation #federation</tag_string>
|
||||
</profile>
|
||||
</post>
|
||||
</XML>
|
||||
"""
|
||||
|
|
Ładowanie…
Reference in New Issue