Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pyc
*egg-info*
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# activity-streams-python

Activity Streams Parser for Python


## Installation

git clone [email protected]:apparentlymart/activity-streams-python.git
cd activity-streams-python
sudo python setup.py install

## Example

from activitystreams.atom import make_activities_from_feed
import urllib2
import xml.etree.ElementTree

url = 'https://github.com/apparentlymart/activity-streams-python/commits/master.atom'
response = urllib2.urlopen(url)
contents = response.read()
xml_tree = xml.etree.ElementTree.fromstring(contents)
xml_tree.getroot = lambda: xml_tree
activities = make_activities_from_feed(xml_tree)
for activity in activities:
print activity.actor.name, activity.verb, activity.object.object_type, activity.object.url
173 changes: 164 additions & 9 deletions activitystreams/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

from rfc3339 import rfc3339

class Activity(object):
actor = None
Expand All @@ -11,7 +11,7 @@ class Activity(object):
service_provider = None
links = None

def __init__(self, actor=None, object=None, target=None, verb=None, time=None, generator=None, icon_url=None, service_provider=None, links=None):
def __init__(self, actor = None, object = None, target = None, verb = None, time = None, generator = None, icon_url = None, service_provider = None, links = None):
self.actor = actor
self.object = object
self.target = target
Expand All @@ -26,12 +26,32 @@ def __init__(self, actor=None, object=None, target=None, verb=None, time=None, g
else:
self.links = []

def to_json(self):
activity_dict = {
'actor': self.actor,
'object': self.object,
'target': self.target,
'verb': self.verb,
'published': self.time,
'service_provider': self.service_provider,
'generator': self.generator,
'icon_url': self.icon_url
}
return jsonify(activity_dict)


class PostActivity(Activity):

pass


class Object(object):
id = None
name = None
url = None
object_type = None
summary = None
content = None
image = None
in_reply_to_object = None
attached_objects = None
Expand All @@ -42,12 +62,13 @@ class Object(object):
downstream_duplicate_ids = None
links = None

def __init__(self, id=None, name=None, url=None, object_type=None, summary=None, image=None, in_reply_to_object=None, attached_objects=None, reply_objects=None, reaction_activities=None, action_links=None, upstream_duplicate_ids=None, downstream_duplicate_ids=None, links=None):
def __init__(self, id = None, name = None, url = None, object_type = None, summary = None, content = None, image = None, in_reply_to_object = None, attached_objects = None, reply_objects = None, reaction_activities = None, action_links = None, upstream_duplicate_ids = None, downstream_duplicate_ids = None, links = None):
self.id = id
self.name = name
self.url = url
self.object_type = object_type
self.summary = summary
self.content = content
self.image = image
self.in_reply_to_object = in_reply_to_object

Expand Down Expand Up @@ -86,6 +107,119 @@ def __init__(self, id=None, name=None, url=None, object_type=None, summary=None,
else:
self.links = []

def to_json(self):
object_dict = {
'id': self.id,
'displayName': self.name,
'url': self.url,
'object_type': self.object_type,
'summary': self.summary,
'content': self.content,
'image': self.image,
'attachments': None,
# 'in_reply_to_object': self.in_reply_to_object,
# 'reply_objects': self.reply_objects,
# 'reaction_activities': self.reaction_activites,
# 'action_links': self.action_links,
'upstream_duplicate_ids': self.upstream_duplicate_ids,
'downstream_duplicate_ids': self.downstream_duplicate_ids
# 'links': self.links,
}
if self.attached_objects:
attachments = []
for obj in self.attached_objects:
attachments.append(obj.to_json())
object_dict['attachments'] = attachments
return jsonify(object_dict)


class NoteObject(Object):

def __init__(self, content, **kwargs):
Object.__init__(self, **kwargs)
self.content = content


class TicketObject(Object):
'''
Proof of concept implemetation. Subject to changes.
'''
ticket_key = None
ticket_summary = None
ticket_type = None
ticket_status = None
ticket_created = None
ticket_closed = None
ticket_description = None
ticket_scope = None
ticket_impact = None
ticket_problem_start = None
ticket_problem_end = None
ticket_maintenance_window_start = None
ticket_maintenance_window_end = None
ticket_update = None
ticket_affected_organisation = None

def __init__(self, id = None, name = None, url = None, object_type = None,
summary = None, image = None, in_reply_to_object = None,
attached_objects = None, reply_objects = None,
reaction_activities = None, action_links = None,
upstream_duplicate_ids = None, downstream_duplicate_ids = None,
links = None, ticket_key = None, ticket_summary = None,
ticket_type = None, ticket_status = None, ticket_created = None,
ticket_closed = None, ticket_description = None, ticket_scope = None,
ticket_impact = None, ticket_problem_start = None,
ticket_problem_end = None, ticket_maintenance_window_start = None,
ticket_maintenance_window_end = None, ticket_update = None,
ticket_affected_organisations = None):
super(TicketObject, self).__init__(id, name, url, object_type, summary, image, in_reply_to_object, attached_objects, reply_objects, reaction_activities, action_links, upstream_duplicate_ids, downstream_duplicate_ids, links)
self.ticket_key = ticket_key
self.ticket_summary = ticket_summary
self.ticket_type = ticket_type
self.ticket_status = ticket_status
self.ticket_created = ticket_created
self.ticket_closed = ticket_closed
self.ticket_description = ticket_description
self.ticket_scope = ticket_scope
self.ticket_impact = ticket_impact
self.ticket_problem_start = ticket_problem_start
self.ticket_problem_end = ticket_problem_end
self.ticket_maintenance_window_start = ticket_maintenance_window_start
self.ticket_maintenance_window_end = ticket_maintenance_window_end
self.ticket_update = ticket_update
self.ticket_affected_organisations = ticket_affected_organisations

if ticket_affected_organisations is not None:
self.ticket_affected_organisations = ticket_affected_organisations
else:
self.ticket_affected_organisations = []

def to_json(self):
object_dict = super(TicketObject, self).to_json()
object_dict['ticket_key'] = self.ticket_key
object_dict['ticket_summary'] = self.ticket_summary
object_dict['ticket_type'] = self.ticket_type
object_dict['ticket_status'] = self.ticket_status
if self.ticket_created:
object_dict['ticket_created'] = rfc3339(self.ticket_created)
if self.ticket_closed:
object_dict['ticket_closed'] = rfc3339(self.ticket_closed)
object_dict['ticket_description'] = self.ticket_description
object_dict['ticket_scope'] = self.ticket_scope
object_dict['ticket_impact'] = self.ticket_impact
if self.ticket_problem_start:
object_dict['ticket_problem_start'] = rfc3339(self.ticket_problem_start)
if self.ticket_problem_end:
object_dict['ticket_problem_end'] = rfc3339(self.ticket_problem_end)
if self.ticket_maintenance_window_start:
object_dict['ticket_maintenance_window_start'] = rfc3339(self.ticket_maintenance_window_start)
if self.ticket_maintenance_window_end:
object_dict['ticket_maintenance_window_end'] = rfc3339(self.ticket_maintenance_window_end)
object_dict['ticket_update'] = self.ticket_update
object_dict['ticket_affected_organisations'] = []
for obj in self.ticket_affected_organisations:
object_dict['ticket_affected_organisations'].append(obj)
return jsonify(object_dict)

class MediaLink(object):
url = None
Expand All @@ -94,19 +228,29 @@ class MediaLink(object):
height = None
duration = None

def __init__(self, url=None, media_type=None, width=None, height=None, duration=None):
def __init__(self, url = None, media_type = None, width = None, height = None, duration = None):
self.url = url
self.media_type = media_type
self.width = width
self.height = height
self.duration = duration

def to_json(self):
medialink_dict = {
'url': self.url,
'media_type': self.media_type,
'width': self.width,
'height': self.height,
'duration': self.duration
}
return medialink_dict


class ActionLink(object):
url = None
caption = None

def __init__(self, url=None, caption=None):
def __init__(self, url = None, caption = None):
self.url = url
self.caption = caption

Expand All @@ -116,12 +260,23 @@ class Link(object):
media_type = None
rel = None

def __init__(self, url=None, media_type=None, rel=None):
def __init__(self, url = None, media_type = None, rel = None):
self.url = url
self.media_type = media_type
self.rel = rel





def jsonify(dictionary):
classes = ['actor', 'generator', 'object', 'provider', 'target', 'author'
'image']
datetimes = ['published', 'updated']
for d in datetimes:
if d in dictionary.keys() and dictionary[d]:
dictionary[d] = rfc3339(dictionary[d])
for c in classes:
if c in dictionary.keys() and dictionary[c]:
dictionary[c] = dictionary[c].to_json()
for k, v in dictionary.items():
if v == []:
dictionary[k] = None
return dictionary
51 changes: 43 additions & 8 deletions activitystreams/atom.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import logging


from activitystreams import Activity, Object, MediaLink, ActionLink, Link
from activitystreams import Activity, Object, MediaLink, ActionLink, Link, NoteObject, PostActivity


import re
import datetime
import time
from HTMLParser import HTMLParser


class AtomActivity(Activity):
pass

def __new__(self, *args, **kwargs):
if kwargs['verb'] == POST_VERB:
return PostActivity(*args, **kwargs)
return Activity.__new__(self, *args, **kwargs)


# This is a weird enum-like thing.
Expand Down Expand Up @@ -53,6 +58,8 @@ def __repr__(self):
MEDIA_HEIGHT = MEDIA_PREFIX + "height"
MEDIA_DURATION = MEDIA_PREFIX + "duration"
MEDIA_DESCRIPTION = MEDIA_PREFIX + "description"
PERSON_OBJECT = "http://activitystrea.ms/schema/1.0/person"
NOTE_OBJECT = "http://activitystrea.ms/schema/1.0/note"


def make_activities_from_feed(et):
Expand All @@ -68,6 +75,7 @@ def make_activities_from_feed(et):


def make_activities_from_entry(entry_elem, feed_elem):

object_elems = entry_elem.findall(ACTIVITY_OBJECT)

activity_is_implied = False
Expand Down Expand Up @@ -112,7 +120,7 @@ def make_activities_from_entry(entry_elem, feed_elem):
target = make_object_from_elem(target_elem, feed_elem, ObjectParseMode.ACTIVITY_OBJECT)

actor = None
if author_elem:
if author_elem is not None:
actor = make_object_from_elem(author_elem, feed_elem, ObjectParseMode.ATOM_AUTHOR)

activities = []
Expand All @@ -122,7 +130,7 @@ def make_activities_from_entry(entry_elem, feed_elem):
else:
object = make_object_from_elem(object_elem, feed_elem, ObjectParseMode.ACTIVITY_OBJECT)

activity = Activity(object=object, actor=actor, target=target, verb=verb, time=published_datetime, icon_url=icon_url)
activity = AtomActivity(object = object, actor = actor, target = target, verb = verb, time = published_datetime, icon_url = icon_url)
activities.append(activity)

return activities
Expand All @@ -140,6 +148,19 @@ def make_object_from_elem(object_elem, feed_elem, mode):
if summary_elem is not None:
summary = summary_elem.text

content = None
content_elem = object_elem.find(ATOM_CONTENT)
if content_elem is not None:
# may be interpre this later
if content_elem.attrib['type'] == 'html':

# ugly fix
content = HTMLParser().unescape(content_elem.text)

#content = content_elem.text
else:
logging.warn('unexpected activity object type %s' % content_elem.attrib['type'])

name_tag_name = ATOM_TITLE
# The ATOM_AUTHOR parsing mode looks in atom:name instead of atom:title
if mode == ObjectParseMode.ATOM_AUTHOR:
Expand All @@ -160,7 +181,7 @@ def make_object_from_elem(object_elem, feed_elem, mode):
if rel == "preview":
if type is None or type == "image/jpeg" or type == "image/gif" or type == "image/png":
# FIXME: Should pull out the width/height/duration attributes from AtomMedia too.
image = MediaLink(url=link_elem.get("href"))
image = MediaLink(url = link_elem.get("href"))

# In the atom:author parse mode we fall back on atom:uri if there's no link rel="alternate"
if url is None and mode == ObjectParseMode.ATOM_AUTHOR:
Expand All @@ -173,7 +194,21 @@ def make_object_from_elem(object_elem, feed_elem, mode):
if object_type_elem is not None:
object_type = object_type_elem.text

return Object(id=id, name=name, url=url, object_type=object_type, image=image, summary=summary)
object_params = {
'id': id,
'name': name,
'url': url,
'object_type': object_type,
'image': image,
'summary': summary,
'content': content
}

if object_type == PERSON_OBJECT:
pass
elif object_type == NOTE_OBJECT:
return NoteObject(**object_params)#content = content,
return Object(**object_params)


# This is pilfered from Universal Feed Parser.
Expand Down Expand Up @@ -249,7 +284,7 @@ def __extract_tzd(m):
minutes = int(minutes)
else:
minutes = 0
offset = (hours*60 + minutes) * 60
offset = (hours * 60 + minutes) * 60
if tzd[0] == '+':
return -offset
return offset
Expand Down
Loading