Skip to content

Commit

Permalink
switch from submodules to virtualenv, package w/setuptools for pypi
Browse files Browse the repository at this point in the history
  • Loading branch information
snarfed committed Jun 21, 2015
1 parent 84126d1 commit 9c74ad8
Show file tree
Hide file tree
Showing 89 changed files with 220 additions and 107 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/build/
/dist/
/.eggs/
/*.egg-info
/local/
datastore.dat
oauth_client_secret
plus-dogfood.json
Expand Down
9 changes: 0 additions & 9 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +0,0 @@
[submodule "oauth_dropins"]
path = oauth_dropins
url = [email protected]:snarfed/oauth-dropins.git
[submodule "beautifulsoup"]
path = beautifulsoup
url = [email protected]:snarfed/beautifulsoup.git
[submodule "mf2py"]
path = mf2py
url = [email protected]:kartikprabhu/mf2py.git
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ http://facebook-activitystreams.appspot.com/
http://twitter-activitystreams.appspot.com/
http://instagram-activitystreams.appspot.com/

It's part of a suite of projects that implement the [OStatus](http://ostatus.org/) federation protocols for the major social networks. The other projects include [portablecontacts-](https://github.com/snarfed/portablecontacts-unofficial), [salmon-](https://github.com/snarfed/salmon-unofficial), [webfinger-](https://github.com/snarfed/webfinger-unofficial), and [ostatus-unofficial](https://github.com/snarfed/ostatus-unofficial).

License: This project is placed in the public domain.


Expand Down Expand Up @@ -136,17 +134,15 @@ Development

Pull requests are welcome! Feel free to [ping me](http://snarfed.org/about) with any questions.

Most dependencies are included as git submodules. Be sure to run `git submodule update --init --recursive` after cloning this repo.

[This ActivityStreams validator](http://activitystreamstester.appspot.com/) is useful for manual testing.

You can run the unit tests with `alltests.py`. If you send a pull request,
please include (or update) a test for the new functionality if possible!
All dependencies are handled by pip and enumerated in
[requirements.txt](https://github.com/snarfed/oauth-dropins/blob/master/requirements.txt).

The tests require the
[App Engine SDK](https://developers.google.com/appengine/downloads). They look
for it in the `GAE_SDK_ROOT` environment variable,
`/usr/local/google_appengine`, or `~/google_appengine`, in that order.
If you send a pull request, please include (or update) a test for the new
functionality if possible! The tests require the
[App Engine SDK](https://developers.google.com/appengine/downloads).
We recommend that you install with pip in a
[`virtualenv`](http://docs.python-guide.org/en/latest/dev/virtualenvs/).
([App Engine details.](https://cloud.google.com/appengine/docs/python/tools/libraries27#vendoring))

Note the `app.yaml.*` files, one for each App Engine app id. To work on or deploy a specific app id, symlink `app.yaml` to its `app.yaml.xxx` file. Likewise, if you add a new site, you'll need to add a corresponding `app.yaml.xxx` file.

Expand All @@ -163,12 +159,14 @@ rm -f app.yaml && ln -s app.yaml.instagram app.yaml && \
git co -- app.yaml
```

[This ActivityStreams validator](http://activitystreamstester.appspot.com/) is useful for manual testing.

To deploy [facebook-atom](https://github.com/snarfed/facebook-atom), [twitter-atom](https://github.com/snarfed/twitter-atom), and [instagram-atom](https://github.com/snarfed/instagram-atom) after an activitystreams-unofficial change:

```shell
#!/bin/tcsh
foreach s (facebook twitter instagram)
cd ~/src/$s-atom/activitystreams && gu && git submodule update && \
cd ~/src/$s-atom/activitystreams && git pull && \
cd .. && ~/google_appengine/appcfg.py --oauth2 update .
end
```
Expand Down
78 changes: 78 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
|ActivityStreams|

This is a library and REST API that fetches and converts social network
data between a wide variety of formats:
- Facebook, Google+, Instagram, and Twitter native APIs
- `ActivityStreams <http://activitystrea.ms/>`__
- `microformats2 <http://microformats.org/wiki/microformats2>`__ HTML
- `microformats2 <http://microformats.org/wiki/microformats2>`__ JSON
- `Atom <http://atomenabled.org/>`__
- XML

You can try it out with these interactive demos:

http://facebook-activitystreams.appspot.com/
http://twitter-activitystreams.appspot.com/
http://instagram-activitystreams.appspot.com/

`Check out the docs for more! <https://github.com/snarfed/activitystreams-unofficial>`__

License: This project is placed in the public domain.


Using
-----

The library and REST API are both based on the `OpenSocial Activity
Streams
service <http://opensocial-resources.googlecode.com/svn/spec/2.0.1/Social-API-Server.xml#ActivityStreams-Service>`__.

Let's start with an example. This code using the library:

.. code:: python
from activitystreams_unofficial import twitter
...
tw = twitter.Twitter(ACCESS_TOKEN_KEY, ACCESS_TOKEN_SECRET)
tw.get_activities(group_id='@friends')
is equivalent to this ``HTTP GET`` request:

::

https://twitter-activitystreams.appspot.com/@me/@friends/@app/
?access_token_key=ACCESS_TOKEN_KEY&access_token_secret=ACCESS_TOKEN_SECRET

They return the authenticated user's Twitter stream, ie tweets from the
people they follow. Here's the JSON output:

.. code:: json
{
"itemsPerPage": 10,
"startIndex": 0,
"totalResults": 12
"items": [{
"verb": "post",
"id": "tag:twitter.com,2013:374272979578150912"
"url": "http://twitter.com/evanpro/status/374272979578150912",
"content": "Getting stuff for barbecue tomorrow. No ribs left! Got some nice tenderloin though. (@ Metro Plus Famille Lemay) http://t.co/b2PLgiLJwP",
"actor": {
"username": "evanpro",
"displayName": "Evan Prodromou",
"description": "Prospector.",
"url": "http://twitter.com/evanpro",
},
"object": {
"tags": [{
"url": "http://4sq.com/1cw5vf6",
"startIndex": 113,
"length": 22,
"objectType": "article"
}, ...],
},
}, ...]
...
}
`Check out the docs for more! <https://github.com/snarfed/activitystreams-unofficial>`__
17 changes: 9 additions & 8 deletions activitystreams.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@
import json
import logging
import urllib
from webob import exc

import appengine_config
import atom
import facebook
import instagram
import microformats2
from oauth_dropins.webutil import handlers
from oauth_dropins.webutil import util
import source
import twitter
from webob import exc

from activitystreams_unofficial import appengine_config
from activitystreams_unofficial import atom
from activitystreams_unofficial import facebook
from activitystreams_unofficial import instagram
from activitystreams_unofficial import microformats2
from activitystreams_unofficial import source
from activitystreams_unofficial import twitter

import webapp2

Expand Down
Empty file.
6 changes: 6 additions & 0 deletions activitystreams_unofficial/appengine_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from oauth_dropins.appengine_config import *

# Suppress BeautifulSoup warning that we let it pick the XML parser instead of
# specifying one explicitly.
import warnings
warnings.filterwarnings('ignore', module='bs4', category=UserWarning)
2 changes: 2 additions & 0 deletions atom.py → activitystreams_unofficial/atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import re
import urlparse

import webapp2
from google.appengine.ext.webapp import template
from oauth_dropins.webutil import util

import microformats2
import source

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Empty file.
32 changes: 17 additions & 15 deletions atom_test.py → activitystreams_unofficial/test/test_atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

import copy

import atom
import facebook_test
import instagram_test
from oauth_dropins.webutil import testutil
import twitter_test

from activitystreams_unofficial import atom

import test_facebook
import test_instagram
import test_twitter


class AtomTest(testutil.HandlerTest):

def test_activities_to_atom(self):
for test_module in facebook_test, instagram_test, twitter_test:
for test_module in test_facebook, test_instagram, test_twitter:
self.assert_multiline_equals(
test_module.ATOM % {'request_url': 'http://request/url',
'host_url': 'http://host/url',
Expand All @@ -26,48 +28,48 @@ def test_activities_to_atom(self):

def test_title(self):
self.assertIn('\n<title>my title</title>',
atom.activities_to_atom([copy.deepcopy(facebook_test.ACTIVITY)],
facebook_test.ACTOR,
atom.activities_to_atom([copy.deepcopy(test_facebook.ACTIVITY)],
test_facebook.ACTOR,
title='my title'))

def test_render_content_as_html(self):
self.assertIn('<a href="https://twitter.com/foo">@twitter</a> meets @seepicturely at <a href="https://twitter.com/search?q=%23tcdisrupt">#tcdisrupt</a> &lt;3 <a href="http://first/link/">first</a> <a href="http://instagr.am/p/MuW67/">instagr.am/p/MuW67</a> ',
atom.activities_to_atom([copy.deepcopy(twitter_test.ACTIVITY)],
twitter_test.ACTOR,
atom.activities_to_atom([copy.deepcopy(test_twitter.ACTIVITY)],
test_twitter.ACTOR,
title='my title'))

def test_render_with_image(self):
"""Attached images are rendered inline as HTML
"""
self.assertIn(
'<img class="thumbnail" src="http://attach/image/big"',
atom.activities_to_atom([copy.deepcopy(instagram_test.ACTIVITY)],
instagram_test.ACTOR,
atom.activities_to_atom([copy.deepcopy(test_instagram.ACTIVITY)],
test_instagram.ACTOR,
title='my title'))

def test_render_untitled_image(self):
"""Images should be included even if there is no other content
"""
activity = copy.deepcopy(instagram_test.ACTIVITY)
activity = copy.deepcopy(test_instagram.ACTIVITY)
del activity['object']['content']
self.assertIn(
'<img class="thumbnail" src="http://attach/image/big"',
atom.activities_to_atom([activity], instagram_test.ACTOR,
atom.activities_to_atom([activity], test_instagram.ACTOR,
title='my title'))

def test_render_encodes_ampersands(self):
# only the one unencoded & in a&b should be encoded
activity = {'object': {'content': 'X <y> http://z?w a&b c&amp;d e&gt;f'}}

out = atom.activities_to_atom([activity], twitter_test.ACTOR, title='my title')
out = atom.activities_to_atom([activity], test_twitter.ACTOR, title='my title')
self.assertIn('X <y> http://z?w a&amp;b c&amp;d e&gt;f', out)
self.assertNotIn('a&b', out)

def test_escape_urls(self):
url = 'http://foo/bar?baz&baj'
activity = {'url': url, 'object': {}}

out = atom.activities_to_atom([activity], twitter_test.ACTOR, title='my title')
out = atom.activities_to_atom([activity], test_twitter.ACTOR, title='my title')
self.assertIn('<id>http://foo/bar?baz&amp;baj</id>', out)
self.assertNotIn(url, out)

Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import urllib
import urllib2

import appengine_config
import facebook
from oauth_dropins.webutil import util
import source
import testutil

from activitystreams_unofficial import appengine_config
from activitystreams_unofficial import facebook
from activitystreams_unofficial import source
from activitystreams_unofficial import testutil


# test data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@
from email.mime.multipart import MIMEMultipart
import json

import appengine_config
import httplib2
from apiclient import discovery
from apiclient import http
import httplib2
from oauth_dropins import googleplus as oauth_googleplus
from oauth_dropins.webutil import util
from oauth_dropins.webutil import testutil

from activitystreams_unofficial import appengine_config
appengine_config.GOOGLE_CLIENT_ID = 'my client id'
appengine_config.GOOGLE_CLIENT_SECRET = 'my client secret'

import googleplus
from oauth_dropins import googleplus as oauth_googleplus
from oauth_dropins.webutil import testutil
from oauth_dropins.webutil import util
from activitystreams_unofficial import googleplus


DISCOVERY_DOC = appengine_config.read('googleplus_api_discovery.json')
Expand Down Expand Up @@ -149,7 +148,7 @@ def tearDown(self):
oauth_googleplus.json_service = None

def init(self, **kwargs):
"""Sets up the API service from googleplus_test_discovery.
"""Sets up the API service from test_googleplus_discovery.
Pass a requestBuilder or http kwarg to inject expected HTTP requests and
responses.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import urllib2
import httplib2

import instagram
import source
from oauth_dropins.webutil import testutil
from activitystreams_unofficial import instagram
from activitystreams_unofficial import source
from activitystreams_unofficial import testutil
from oauth_dropins.webutil import util


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

__author__ = ['Ryan Barrett <[email protected]>']

import microformats2
from activitystreams_unofficial import microformats2
import re
from oauth_dropins.webutil import testutil
from activitystreams_unofficial import testutil


class Microformats2Test(testutil.HandlerTest):
Expand Down
Loading

0 comments on commit 9c74ad8

Please sign in to comment.