pep8speaks: Hello @jaywink! Thanks for submitting the PR.



 - In the file [`federation/entities/diaspora/entities.py`](https://github.com/jaywink/federation/blob/7c5d86a3475b96eabaf6f7af37005ed5ebb82024/federation/entities/diaspora/entities.py), following are the PEP8 issues :

> [Line 122:1](https://github.com/jaywink/federation/blob/7c5d86a3475b96eabaf6f7af37005ed5ebb82024/federation/entities/diaspora/entities.py#L122): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace












diff --git a/federation/entities/base.py b/federation/entities/base.py
index 1185248..97a055a 100644
--- a/federation/entities/base.py
+++ b/federation/entities/base.py
@@ -5,7 +5,7 @@ from dirty_validators.basic import Email
 
 
 __all__ = (
-    "Post", "Image", "Comment", "Reaction", "Relationship", "Profile", "Retraction"
+    "Post", "Image", "Comment", "Reaction", "Relationship", "Profile", "Retraction", "Follow",
 )
 
 
@@ -266,6 +266,21 @@ class Relationship(CreatedAtMixin, HandleMixin):
             ))
 
 
+class Follow(CreatedAtMixin, HandleMixin):
+    """Represents a handle following or unfollowing another handle."""
+    target_handle = ""
+    following = True
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._required += ["target_handle", "following"]
+
+    def validate_target_handle(self):
+        validator = Email()
+        if not validator.is_valid(self.target_handle):
+            raise ValueError("Target handle is not valid")
+
+
 class Profile(CreatedAtMixin, HandleMixin, OptionalRawContentMixin, PublicMixin, GUIDMixin):
     """Represents a profile for a user."""
     name = ""
diff --git a/federation/entities/diaspora/entities.py b/federation/entities/diaspora/entities.py
index e81bfa6..39d00ca 100644
--- a/federation/entities/diaspora/entities.py
+++ b/federation/entities/diaspora/entities.py
@@ -1,6 +1,6 @@
 from lxml import etree
 
-from federation.entities.base import Comment, Post, Reaction, Relationship, Profile, Retraction, BaseEntity
+from federation.entities.base import Comment, Post, Reaction, Relationship, Profile, Retraction, BaseEntity, Follow
 from federation.entities.diaspora.utils import format_dt, struct_to_xml, get_base_attributes
 from federation.exceptions import SignatureVerificationError
 from federation.protocols.diaspora.signatures import verify_relayable_signature, create_relayable_signature
@@ -104,7 +104,7 @@ class DiasporaLike(DiasporaRelayableMixin, Reaction):
 
 
 class DiasporaRequest(DiasporaEntityMixin, Relationship):
-    """Diaspora relationship request."""
+    """Diaspora legacy request."""
     relationship = "sharing"
 
     def to_xml(self):
@@ -117,6 +117,24 @@ class DiasporaRequest(DiasporaEntityMixin, Relationship):
         return element
 
 
+class DiasporaContact(DiasporaEntityMixin, Follow):
+    """Diaspora contact.
+
+    Note we don't implement 'sharing' at the moment so just send it as the same as 'following'.
+    """
+
+    def to_xml(self):
+        """Convert to XML message."""
+        element = etree.Element("contact")
+        struct_to_xml(element, [
+            {"author": self.handle},
+            {"recipient": self.target_handle},
+            {"following": "true" if self.following else "false"},
+            {"sharing": "true" if self.following else "false"},
+        ])
+        return element
+
+
 class DiasporaProfile(DiasporaEntityMixin, Profile):
     """Diaspora profile."""
 
diff --git a/federation/entities/diaspora/mappers.py b/federation/entities/diaspora/mappers.py
index 57828b3..22a7cd3 100644
--- a/federation/entities/diaspora/mappers.py
+++ b/federation/entities/diaspora/mappers.py
@@ -3,10 +3,10 @@ from datetime import datetime
 
 from lxml import etree
 
-from federation.entities.base import Image, Relationship, Post, Reaction, Comment, Profile, Retraction
+from federation.entities.base import Image, Relationship, Post, Reaction, Comment, Profile, Retraction, Follow
 from federation.entities.diaspora.entities import (
     DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest, DiasporaProfile, DiasporaRetraction,
-    DiasporaRelayableMixin)
+    DiasporaRelayableMixin, DiasporaContact)
 from federation.protocols.diaspora.signatures import get_element_child_info
 from federation.utils.diaspora import retrieve_and_parse_profile
 
@@ -20,16 +20,19 @@ MAPPINGS = {
     "request": DiasporaRequest,
     "profile": DiasporaProfile,
     "retraction": DiasporaRetraction,
+    "contact": DiasporaContact,
 }
 
 TAGS = [
     # Order is important. Any top level tags should be before possibly child tags
-    "status_message", "comment", "like", "request", "profile", "retraction", "photo",
+    "status_message", "comment", "like", "request", "profile", "retraction", "photo", "contact",
 ]
 
 BOOLEAN_KEYS = (
     "public",
     "nsfw",
+    "following",
+    "sharing",
 )
 
 DATETIME_KEYS = (
@@ -205,7 +208,8 @@ def get_outbound_entity(entity, private_key):
     """
     outbound = None
     cls = entity.__class__
-    if cls in [DiasporaPost, DiasporaRequest, DiasporaComment, DiasporaLike, DiasporaProfile, DiasporaRetraction]:
+    if cls in [DiasporaPost, DiasporaRequest, DiasporaComment, DiasporaLike, DiasporaProfile, DiasporaRetraction,
+               DiasporaContact]:
         # Already fine
         outbound = entity
     elif cls == Post:
@@ -219,6 +223,8 @@ def get_outbound_entity(entity, private_key):
         if entity.relationship in ["sharing", "following"]:
             # Unfortunately we must send out in both cases since in Diaspora they are the same thing
             outbound = DiasporaRequest.from_base(entity)
+    elif cls == Follow:
+        outbound = DiasporaContact.from_base(entity)
     elif cls == Profile:
         outbound = DiasporaProfile.from_base(entity)
     elif cls == Retraction:

Add support for Diaspora contact payload
This replaces the old `request` payload. See diaspora/diaspora_federation#32
diff --git a/federation/tests/entities/diaspora/test_entities.py b/federation/tests/entities/diaspora/test_entities.py
index 32f8b2c..23d0ac0 100644
--- a/federation/tests/entities/diaspora/test_entities.py
+++ b/federation/tests/entities/diaspora/test_entities.py
@@ -6,7 +6,7 @@ from lxml import etree
 from federation.entities.base import Profile
 from federation.entities.diaspora.entities import (
     DiasporaComment, DiasporaPost, DiasporaLike, DiasporaRequest, DiasporaProfile, DiasporaRetraction,
-)
+    DiasporaContact)
 from federation.exceptions import SignatureVerificationError
 from federation.tests.fixtures.keys import get_dummy_private_key
 
@@ -81,6 +81,14 @@ class TestEntitiesConvertToXML():
                     b"<target_guid>xxxxxxxxxxxxxxxx</target_guid><target_type>Post</target_type></retraction>"
         assert etree.tostring(result) == converted
 
+    def test_contact_to_xml(self):
+        entity = DiasporaContact(handle="alice@example.com", target_handle="bob@example.org", following=True)
+        result = entity.to_xml()
+        assert result.tag == "contact"
+        converted = b"<contact><author>alice@example.com</author><recipient>bob@example.org</recipient>" \
+                    b"<following>true</following><sharing>true</sharing></contact>"
+        assert etree.tostring(result) == converted
+
 
 class TestDiasporaProfileFillExtraAttributes():
     def test_raises_if_no_handle(self):
diff --git a/federation/tests/entities/diaspora/test_mappers.py b/federation/tests/entities/diaspora/test_mappers.py
index 1516ae4..bd2b96b 100644
--- a/federation/tests/entities/diaspora/test_mappers.py
+++ b/federation/tests/entities/diaspora/test_mappers.py
@@ -4,18 +4,17 @@ from unittest.mock import patch, Mock
 import pytest
 
 from federation.entities.base import (
-    Comment, Post, Reaction, Relationship, Profile, Retraction, Image
-)
+    Comment, Post, Reaction, Relationship, Profile, Retraction, Image,
+    Follow)
 from federation.entities.diaspora.entities import (
     DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest,
-    DiasporaProfile, DiasporaRetraction
-)
+    DiasporaProfile, DiasporaRetraction, DiasporaContact)
 from federation.entities.diaspora.mappers import message_to_objects, get_outbound_entity
 from federation.tests.fixtures.keys import get_dummy_private_key
 from federation.tests.fixtures.payloads import (
     DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE,
     DIASPORA_REQUEST, DIASPORA_PROFILE, DIASPORA_POST_INVALID, DIASPORA_RETRACTION,
-    DIASPORA_POST_WITH_PHOTOS, DIASPORA_POST_LEGACY_TIMESTAMP, DIASPORA_POST_LEGACY)
+    DIASPORA_POST_WITH_PHOTOS, DIASPORA_POST_LEGACY_TIMESTAMP, DIASPORA_POST_LEGACY, DIASPORA_CONTACT)
 
 
 def mock_fill(attributes):
@@ -155,6 +154,15 @@ class TestDiasporaEntityMappersReceive():
         assert entity.target_guid == "x" * 16
         assert entity.entity_type == "Post"
 
+    def test_message_to_objects_contact(self):
+        entities = message_to_objects(DIASPORA_CONTACT)
+        assert len(entities) == 1
+        entity = entities[0]
+        assert isinstance(entity, DiasporaContact)
+        assert entity.handle == "alice@example.com"
+        assert entity.target_handle == "bob@example.org"
+        assert entity.following is True
+
     @patch("federation.entities.diaspora.mappers.logger.error")
     def test_invalid_entity_logs_an_error(self, mock_logger):
         entities = message_to_objects(DIASPORA_POST_INVALID)
@@ -191,6 +199,8 @@ class TestGetOutboundEntity():
         assert get_outbound_entity(entity, dummy_key) == entity
         entity = DiasporaProfile()
         assert get_outbound_entity(entity, dummy_key) == entity
+        entity = DiasporaContact()
+        assert get_outbound_entity(entity, dummy_key) == entity
 
     def test_post_is_converted_to_diasporapost(self):
         entity = Post()
@@ -229,6 +239,10 @@ class TestGetOutboundEntity():
         entity = Retraction()
         assert isinstance(get_outbound_entity(entity, get_dummy_private_key()), DiasporaRetraction)
 
+    def test_follow_is_converted_to_diasporacontact(self):
+        entity = Follow()
+        assert isinstance(get_outbound_entity(entity, get_dummy_private_key()), DiasporaContact)
+
     def test_signs_relayable_if_no_signature(self):
         entity = DiasporaComment()
         dummy_key = get_dummy_private_key()
diff --git a/federation/tests/entities/test_base.py b/federation/tests/entities/test_base.py
index 6035868..5eaa10a 100644
--- a/federation/tests/entities/test_base.py
+++ b/federation/tests/entities/test_base.py
@@ -4,7 +4,7 @@ import pytest
 
 from federation.entities.base import (
     BaseEntity, Relationship, Profile, RawContentMixin, GUIDMixin, HandleMixin, PublicMixin, Image, Retraction,
-)
+    Follow)
 from federation.tests.factories.entities import TaggedPostFactory, PostFactory
 
 
@@ -160,3 +160,18 @@ class TestRetractionEntity():
         )
         with pytest.raises(ValueError):
             entity.validate()
+
+
+class TestFollowEntity():
+    def test_instance_creation(self):
+        entity = Follow(
+            handle="foo@example.com", target_handle="bar@example.org", following=True
+        )
+        entity.validate()
+
+    def test_required_validates(self):
+        entity = Follow(
+            handle="foo@example.com", following=True
+        )
+        with pytest.raises(ValueError):
+            entity.validate()
diff --git a/federation/tests/fixtures/payloads.py b/federation/tests/fixtures/payloads.py
index b58c95e..ead44d5 100644
--- a/federation/tests/fixtures/payloads.py
+++ b/federation/tests/fixtures/payloads.py
@@ -173,3 +173,12 @@ DIASPORA_RETRACTION = """
         <target_type>Post</target_type>
     </retraction>
 """
+
+DIASPORA_CONTACT = """
+    <contact>
+        <author>alice@example.com</author>
+        <recipient>bob@example.org</recipient>
+        <following>true</following>
+        <sharing>true</sharing>
+    </contact>
+"""

pip install -e .[dev]
parse_log_pytest
requirements.txt
apt-get update apt-get install -y libxml2-dev libxslt1-dev
3.6
dev-requirements.txt
py.test --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
arrow==1.2.3
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
codecov==2.1.13
colorama==0.4.5
commonmark==0.9.1
coverage==6.2
cssselect==1.1.0
dirty-validators==0.5.4
docutils==0.18.1
factory-boy==3.2.1
Faker==14.2.1
-e git+https://github.com/jaywink/federation.git@e0dd39d518136eef5d36fa3c2cb36dc4b32d4a63#egg=federation
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
isodate==0.6.1
Jinja2==3.0.3
jsonschema==3.2.0
livereload==2.6.3
lxml==5.3.1
MarkupSafe==2.0.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pycrypto==2.6.1
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
pytest-cov==4.0.0
pytest-warnings==0.3.1
python-dateutil==2.9.0.post0
python-xrd==0.1
pytz==2025.2
recommonmark==0.7.1
requests==2.27.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-autobuild==2021.3.14
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==1.2.3
tornado==6.1
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: federation
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - arrow==1.2.3
      - attrs==22.2.0
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - codecov==2.1.13
      - colorama==0.4.5
      - commonmark==0.9.1
      - coverage==6.2
      - cssselect==1.1.0
      - dirty-validators==0.5.4
      - docutils==0.18.1
      - factory-boy==3.2.1
      - faker==14.2.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - isodate==0.6.1
      - jinja2==3.0.3
      - jsonschema==3.2.0
      - livereload==2.6.3
      - lxml==5.3.1
      - markupsafe==2.0.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pycrypto==2.6.1
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-warnings==0.3.1
      - python-dateutil==2.9.0.post0
      - python-xrd==0.1
      - pytz==2025.2
      - recommonmark==0.7.1
      - requests==2.27.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-autobuild==2021.3.14
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==1.2.3
      - tornado==6.1
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/federation

federation/tests/entities/diaspora/test_entities.py::TestEntitiesConvertToXML::test_post_to_xml federation/tests/entities/diaspora/test_entities.py::TestEntitiesConvertToXML::test_comment_to_xml federation/tests/entities/diaspora/test_entities.py::TestEntitiesConvertToXML::test_like_to_xml federation/tests/entities/diaspora/test_entities.py::TestEntitiesConvertToXML::test_request_to_xml federation/tests/entities/diaspora/test_entities.py::TestEntitiesConvertToXML::test_profile_to_xml federation/tests/entities/diaspora/test_entities.py::TestEntitiesConvertToXML::test_retraction_to_xml federation/tests/entities/diaspora/test_entities.py::TestEntitiesConvertToXML::test_contact_to_xml federation/tests/entities/diaspora/test_entities.py::TestDiasporaProfileFillExtraAttributes::test_raises_if_no_handle federation/tests/entities/diaspora/test_entities.py::TestDiasporaProfileFillExtraAttributes::test_calls_retrieve_and_parse_profile federation/tests/entities/diaspora/test_entities.py::TestDiasporaRetractionEntityConverters::test_entity_type_from_remote federation/tests/entities/diaspora/test_entities.py::TestDiasporaRetractionEntityConverters::test_entity_type_to_remote federation/tests/entities/diaspora/test_entities.py::TestDiasporaRelayableEntitySigning::test_signing_comment_works federation/tests/entities/diaspora/test_entities.py::TestDiasporaRelayableEntitySigning::test_signing_like_works federation/tests/entities/diaspora/test_entities.py::TestDiasporaRelayableEntityValidate::test_raises_if_no_sender_key federation/tests/entities/diaspora/test_entities.py::TestDiasporaRelayableEntityValidate::test_calls_verify_signature federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_simple_post federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_post_legacy federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_legact_timestamp federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_post_with_photos federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_comment federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_like federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_request federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_profile federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_retraction federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_message_to_objects_contact federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_invalid_entity_logs_an_error federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_adds_source_protocol_to_entity federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_element_to_objects_calls_sender_key_fetcher federation/tests/entities/diaspora/test_mappers.py::TestDiasporaEntityMappersReceive::test_element_to_objects_calls_retrieve_remote_profile federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_already_fine_entities_are_returned_as_is federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_post_is_converted_to_diasporapost federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_comment_is_converted_to_diasporacomment federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_reaction_of_like_is_converted_to_diasporalike federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_relationship_of_sharing_or_following_is_converted_to_diasporarequest federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_profile_is_converted_to_diasporaprofile federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_other_reaction_raises federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_other_relation_raises federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_retraction_is_converted_to_diasporaretraction federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_follow_is_converted_to_diasporacontact federation/tests/entities/diaspora/test_mappers.py::TestGetOutboundEntity::test_signs_relayable_if_no_signature federation/tests/entities/test_base.py::TestPostEntityTags::test_post_entity_returns_list_of_tags federation/tests/entities/test_base.py::TestPostEntityTags::test_post_entity_without_raw_content_tags_returns_empty_set federation/tests/entities/test_base.py::TestBaseEntityCallsValidateMethods::test_entity_calls_attribute_validate_method federation/tests/entities/test_base.py::TestBaseEntityCallsValidateMethods::test_entity_calls_main_validate_methods federation/tests/entities/test_base.py::TestBaseEntityCallsValidateMethods::test_validate_children federation/tests/entities/test_base.py::TestGUIDMixinValidate::test_validate_guid_raises_on_low_length federation/tests/entities/test_base.py::TestHandleMixinValidate::test_validate_handle_raises_on_invalid_format federation/tests/entities/test_base.py::TestPublicMixinValidate::test_validate_public_raises_on_low_length federation/tests/entities/test_base.py::TestEntityRequiredAttributes::test_entity_checks_for_required_attributes federation/tests/entities/test_base.py::TestEntityRequiredAttributes::test_validate_checks_required_values_are_not_empty federation/tests/entities/test_base.py::TestRelationshipEntity::test_instance_creation federation/tests/entities/test_base.py::TestRelationshipEntity::test_instance_creation_validates_relationship_value federation/tests/entities/test_base.py::TestRelationshipEntity::test_instance_creation_validates_target_handle_value federation/tests/entities/test_base.py::TestProfileEntity::test_instance_creation federation/tests/entities/test_base.py::TestProfileEntity::test_instance_creation_validates_email_value federation/tests/entities/test_base.py::TestProfileEntity::test_guid_is_mandatory federation/tests/entities/test_base.py::TestImageEntity::test_instance_creation federation/tests/entities/test_base.py::TestImageEntity::test_required_fields federation/tests/entities/test_base.py::TestRetractionEntity::test_instance_creation federation/tests/entities/test_base.py::TestRetractionEntity::test_required_validates federation/tests/entities/test_base.py::TestFollowEntity::test_instance_creation federation/tests/entities/test_base.py::TestFollowEntity::test_required_validates
federation/entities/base.py federation/entities/diaspora/mappers.py federation/entities/diaspora/entities.py
federation/entities/base.py federation/entities/diaspora/mappers.py federation/entities/diaspora/entities.py