python-pypump/0000755000175000017500000000000012222050373012033 5ustar tagtagpython-pypump/tests/0000755000175000017500000000000012222046743013203 5ustar tagtagpython-pypump/tests/__init__.py0000644000175000017500000000000012222046743015302 0ustar tagtagpython-pypump/docs/0000755000175000017500000000000012222046774012775 5ustar tagtagpython-pypump/docs/index.rst0000644000175000017500000000225412222046743014635 0ustar tagtag.. PyPump documentation master file, created by sphinx-quickstart on Mon May 27 12:57:47 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. PyPump - Python Pump.io Library =============================== PyPump provides a simple, powerful and pythonic way of interfacing with `pump.io `_. The community for PyPump lives on IRC in the #pypump channel on `MegNet `_. PyPump is under the `GPLv3 `_. You should probably look to see if/how this may impact you and your programs if you desire to use PyPump. Getting started --------------- If you're new to PyPump and feeling a bit overwhelmed the :doc:`tutorial` is the best place to go. Contents -------- .. toctree:: :maxdepth: 1 gettingstarted/installing gettingstarted/qnd gettingstarted/tutorial gettingstarted/authentication gettingstarted/verifier_callback gettingstarted/webpump Models ------ .. toctree:: :maxdepth: 1 models/inbox models/note models/comment models/image models/person models/activity This was build on |today| python-pypump/docs/conf.py0000644000175000017500000001720012222046743014270 0ustar tagtag# -*- coding: utf-8 -*- # # PyPump documentation build configuration file, created by # sphinx-quickstart on Mon May 27 12:57:47 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'PyPump' copyright = u'2013, Jessica Tallon' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.0.0' # The full version, including alpha/beta/rc tags. release = '1.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'PyPumpdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'PyPump.tex', u'PyPump Documentation', u'Jessica Tallon', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pypump', u'PyPump Documentation', [u'Jessica Tallon'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'PyPump', u'PyPump Documentation', u'Jessica Tallon', 'PyPump', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' python-pypump/docs/Makefile0000644000175000017500000001267412222046743014443 0ustar tagtag# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyPump.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyPump.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PyPump" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyPump" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-pypump/docs/gettingstarted/0000755000175000017500000000000012222046743016021 5ustar tagtagpython-pypump/docs/gettingstarted/authentication.rst0000644000175000017500000000356412222046743021602 0ustar tagtag============= Authorization ============= What you need to know --------------------- Pump.io uses oauth 1.0 with dynamic client registration, this available through a lot of libraries, PyPump uses `oauthlib `_ and a wrapper around it to provide an provide an interface with the `requests `_ library - `requests-oauthlib `. All of that is handled by PyPump however there are some things to know. OAuth works by exchanging pre-established client credentials and token, you however have to provide those each time you make instantiate the PyPump object. You will have to provide a mechanism to store these so that you can you can provide them the next time. Example ------- The following will create (for the first time) a connection to a pump.io server for the user Tsyesika@io.theperplexingpariah.co.uk for my client named "Test.io":: >>> from pypump import PyPump >>> pump = PyPump("Tsyesika@io.theperplexingpariah.co.uk", client_name="Test.io") >>> client_credentials = pump.get_registration() # will return [, , ] >>> client_tokens = pump.get_token() # will return [, ] .. note:: If you're not using CLI you will need to override the *get_access* method on PyPump to ask for their varification token An example of then connecting again (using the same variable names as above). This will produce a PyPump object which will use the same credentials as established above:: >>> pump = PyPump( ... "pump.megworld.co.uk", ... key=client_credentials[0], # the client key ... secret=client_credentials[1], # the client secret ... token=client_tokens[0], # the token key ... token_secret=client_tokens[1], # the token secret ... ) python-pypump/docs/gettingstarted/qnd.rst0000644000175000017500000001011612222046743017334 0ustar tagtag=============== Quick 'n Dirty! =============== Introduction ------------ .. warning:: This is not complete, this is used as a fast intro for those fairly familiar or a reference for those who are a little rusty with PyPump This guide is designed to get you up to speed and using this library in a very short amount of time, to do that I avoid long winded explanations, if you're completely new to PyPump and/or pump.io please use :doc:`tutorial`. Getting connected ----------------- So we need to get started:: >>> from pypump import PyPump First time lets do all the oauth stuff:: >>> pump = PyPump("me@my.server.org", client_name="Quick 'n dirty") Super, next, I wanna see my inbox:: >>> me = pump.Person("me@my.server.org") >>> my_inbox = me.inbox >>> for activity in my_inbox[:20]: ... print activity .. note:: iterating over the inbox without any slice will iterate until the very first note in your inbox/feed/outbox Oh, my friend Evan isn't there, I probably need to follow him:: >>> evan = pump.Person("evan@e14n.org") >>> evan.follow() Awesome. Lets check again:: >>> for activity in my_inbox[:20]: ... print activity Evan likes PyPump, super!:: >>> activity = my_inbox[1] # second activity in my inbox >>> awesome_note = activity.obj >>> awesome_note.content 'Oh wow, PyPump is awesome!' >>> awesome_note.like() I wonder if someone else has liked that:: >>> awesome_note.likes [me@my.server.org, joar@some.other.server] Cool! Lets tell them about these docs:: >>> my_comment = pump.Comment("Guys, if you like PyPump check out the docs!") >>> awesome_note.comment(my_comment) I wonder what was posted last:: >>> latest_activity = my_inbox[0] >>> print latest_activity Oh it's an image, lets see the thumb nail:: >>> url = latest_activity.obj.thumb_url >>> fout = open("some_image.{0}".format(url.split(".")[-1], "wb") >>> import urllib2 # this will be different with python3 >>> fout.write(urllib2.urlopen(url).read()) >>> fout.close() Hmm, I want to see a bigger version:: >>> large_url = latest_activity.obj.full_url >>> print large_url >>> # you will find Images often hold other pump.Image objects, we just need to extra the url >>> large_url = large_url.url >>> fout = open("some_image_larger.{0}".format(large_url.split(".")[-1], "wb") >>> fout.write(urllib2.urlopen(url).read()) >>> fout.close() That looks awesome, lets post a comment:: >>> my_comment = pump.Comment("Great, super imaeg") >>> latest_activity.obj.comment(my_comment) Oh no, I made a typo:: >>> my_comment.delete() >>> my_comment.content = "Great, super image") >>> latest_activity.obj.comment(my_comment) Much better! Lets make a note to tell people how easy this all is:: >>> my_note = pump.Note("My gawd... PyPump is super easy to get started with") >>> my_note.send() But hold on though, that only sent it to followers? What gives:: >>> awesome_pump = pump.Note("PyPump is really awesome!") >>> awesome_pump.to = pump.Public >>> awesome_pump.cc = (pump.Followers, pump.Following, pump.Person("MyFriend@server.com")) >>> awesome_pump.send() Oh cool that's sent to all my friends, So can i make my own lists:: >>> for my_list in pump.List.all(): ... print my_list.name Coworkers Family Friends Oh are all those my lists that are defined. How do I send a note to them?:: >>> new_note = pump.Note("Work sucks!") >>> new_note.to = pump.List("Coworkers") >>> new_note.cc = pump.List("Friends) So, can i send something to all of of the groups I made? Yep:: >>> another_note = pump.Note("This really goes to everyone in my groups?") >>> another_note.to = pump.List.all() >>> another_note.cc = (pump.Person("moggers87@microca.st"), pump.Person("cwebber@identi.ca")) >>> another_note.send() Don't forget is there are any issues please issue them on our `GitHub `_! python-pypump/docs/gettingstarted/verifier_callback.rst0000644000175000017500000000551312222046743022206 0ustar tagtag================ Getting Verifier ================ The default behavour of PyPump is to ask the user (in english) to click on a link and paste in the verifier. It's done via a print statement and raw_input which usually occur over standard in and standard out. Often this isn't very useful so PyPump provides a very easy way using two callbacks to work with your program to ask them for the verifier. We have a link the user must click, it'll take them to an Authorization page asking them to permit your program access, if they allow your program they will be taken to a page that will have the verifier on, this needs to be entered into your program. The default experiance via PyPump ---------------------------------- This is how PyPump by default asks:: To allow us to use your pump.io please follow the instructions at: https://some.server/oauth/authorize?oauth_token=b_Tf-y1yXrPbfkOXj-PhFQ Callbacks --------- The callbacks work by you define a callback for when PyPump would like to be called with the `url` when PyPump would like you to ask the user for the verifier the prototype of the function is:: def ask_verifier(self, url): """ This should display the url and ask the user to authorize your app """ pass # pypump ignores the return value Once you have the verifier you need to call `verifier`:: >>> verifier c_Fe-91tXrPbfkOXj-xKFu >>> pypump_instance.verifier(verifier) Example ------- I wrote this, it's not a real GUI framework, I didn't want to weight this down with a lot of other cruft, this is just to show a rough example of how this might work:: from pypump import PyPump from gui_framework import Window, Widgets class MyGui(object): pump = None def __init__(self): self.window = Window() self.window.add( Widgets.Message("Checking authorization...") ) def return_verifier(self): """ Hands the verifier back to PyPump """ if self.pump is None: raise Exception("You need to set PyPump") verifier = self.verifier.get() self.pump.verifier(verifier) # Done! def ask_verifier(self, url): """ Will display a message with URL and a text box for the verifier """ self.window.clear() self.verifier = Widgets.Textbox() self.button = Widgets.Button() self.window.add( Widgets.Message("Please authorize me!"), Widgets.Message(url), self.verifier, button, ) self.button.when_clicked(self.return_verifier) gui = MyGui() pump = PyPump( "someome@server.org", client_name="MyClient", verifier_callback=gui.ask_verifier ) gui.pump = pump python-pypump/docs/gettingstarted/tutorial.rst0000644000175000017500000001532312222046743020422 0ustar tagtag======== Tutorial ======== .. note:: This tutorial is prep for an API that does not yet actually exist... we're doing the documentation driven development route. But this is what it should look like! ;) PyPump and the Pump API ----------------------- PyPump is aiming to implement and interface with the `Pump API `_, which is a federation protocol for the web. You can read the actual Pump API docs to get a sense of all that, but here's a high level overview. The Pump API is all about ActivityStreams and sending json-encoded descriptions of activities back and forth across different users on different sites. At the highest conceptual level, it's not too different from the idea of email servers sending emails back and forth, but the messages (activities here) are much more specific and carry more specific meaning about what "type" of message is being sent back and forth. An activity can be a user "favoriting" something or "posting an image" or what have you. In the world of email, each user has an email address; in the world of Pump, each user has a `webfinger `_ address. It looks pretty similar, but it's meant for the web. For the sake of this tutorial, you don't need to know how webfinger works; the PyPump API will handle that for you. Each user has two main feeds that are used for communication. In the Pump API docs' own wording: - An **activity outbox** (probably at /api/user//feed). This is where the user posts new activities, and where others can read the user's activities. - An **activity inbox** (probably at /api/user//inbox). This is where the user can read posts that were sent to him/her. Remote servers can post activities here to be delivered to the user. (We use the inbox/outbox convention fairly strongly in PyPump.) You should read the Pump spec, but sometimes coding examples are the best way to learn. So, that said, let's get into an example of using PyPump! A quick example --------------- Let's assume you already have a user with the webfinger id of mizbunny@example.org. We want to check what our latest messages are! But before we can do that, we need to authenticate. If this is your first time, you need to authenticate this client:: >>> from pypump import PyPump >>> pump = PyPump("mizbunny@example.org", client_name="Test.io") >>> client_credentials = pump.get_registration() # will return [, ] >>> client_tokens = pump.get_token() The PyPump call will try to verify with OAuth, You may wish to override how it asks for authentication. PyPump by default writes to standard out a URL for the user to click and reads in from standard in for a verification code presented by the webserver. You should store the client credentials somewhere. You can now reconnect like so:: >>> pump = PyPump( ... "mizbunny@example.org", ... key=client_credentials[0], # the client key ... secret=client_credentials[1], # the client secret ... token=client_tokens[0], # the token key ... token_secret=client_tokens[1], # the token secret ... ) Okay, we're connected! Next up, we want to check out what our last 30 items in our inbox are, but first we need to find ourselves:: >>> me = pump.Person("mizbunny@example.org") >>> me.summary >>> 'Hello and welcome to my summary' That looks like us, now to find our inbox items. The inbox comes in three versions - me.inbox.major is where major activities such as posted notes and images end up. - me.inbox.minor is where minor activities such as likes and comments end up. - me.inbox is a combination of both of the above. We only want to see notes, so we use the major inbox. The inbox supports python-style index slicing:: >>> recent_activities = me.inbox.major[:30] # get last 30 activities We could print out each of the most recent activities like so:: >>> for activity in recent_activities: >>> print activity ... Maybe we're just looking at our most recent message, and see it's from our friend Evan. It seems that he wants to invite us over for a dinner party:: >>> activity = recent_activities[0] >>> activity >>> message = activity.obj >>> message.author >>> message.content "Yo, want to come over to dinner? We're making asparagus!" We can comment on the message saying we'd love to:: >>> our_reply = pump.Comment("I'd love to!") >>> message.comment(our_reply) # this is evans message we got above! (Since this Note activity is being instantiated, it needs a reference to our PyPump class instance. Objects that you get back and forth from the API themselves will try to keep track of their own parent PyPump object for you.) We could even like/favourite the previous message:: >>> message.like() We can also check to see what our buddy's public feed is. Maybe he's said some interesting things?:: >>> evan = message.author >>> for activity in evan.outbox: >>> message = activity.obj >>> print message.content Prehaps we want to know a bit about Evan:: >>> print evan.summary .. Maybe we took a picture, and we want to post that picture to our .. public feed so everyone can see it. We can do this by posting it to .. our outbox: .. .. >>> from pypump.activities import Photo .. >>> new_photo = Photo( .. ... pump, .. ... subject= Want to see what the model actually looks like? All activities in pump.io have a .seralize method:: >>> print message.to_json(indent=2) { "id": "http://coding.example/api/activity/bwkflwken", "actor": { "id": "acct:bwk@coding.example", "objectType": "person", "displayName": "Brian Kernighan" }, "verb": "follow", "to": [{ "id": "acct:ken@coding.example", "objectType": "person" }], "object": { "id": "acct:ken@coding.example", "objectType": "person", "displayName": "Ken Thompson" }, "published": "1974-01-01T00:00:00", "links": [ {"rel": "self", "href": "http://coding.example/api/activity/bwkflwken"} ] } (The indent attribute here is passed to to give prettier output.) .. (Yes, that was stolen from the Pump API docs :)) (similarly, all activity classes provide a unserialize class method). .. Things missing: - How to post to your public feed, as opposed to a list of specific people? - Show different types of activities - Explain how to implement an activity subclass? python-pypump/docs/gettingstarted/installing.rst0000644000175000017500000000207012222046743020716 0ustar tagtag============= Installation ============= Using pip --------- The best way to install PyPump is via pip, if you haven't, setup:: $ virtualenv path/to/virtualenv $ source path/to/virtualenv/bin/activate $ pip install pypump If you get an error which looks like:: Could not find a version that satisfies the requirement pypump (from versions: 0.1.2a-, 0.1.5a, 0.1.3a-, 0.1.4a, 0.1.1a, 0.1.6a, 0.1.7a, 0.1.8a, 0.1.9a, 0.2, 0.1a) Cleaning up... No distributions matching the version for pypump You need to specify the latest version, for example:: $ pip install pypump==0.2 Using git --------- .. Warning:: The code on git may break, the code on pip is likely to be much more stable You can if you want the latest and greatest use the copy on git, to do this execute:: $ git clone https://github.com/xray7224/PyPump.git $ cd PyPump $ virtualenv .vt_env && . .vt_env/bin/activate $ pip install requests oauthlib requests-oauthlib To keep this up to date use the following command inside the PyPump folder:: $ git pull python-pypump/docs/gettingstarted/webpump.rst0000644000175000017500000000724712222046743020244 0ustar tagtagWeb Development using PyPump =========================== One of the problem with PyPump and Web development is that you often have a view which is called and then must return a function. While it is possible it may be difficult to use the regular PyPump callback routines. WebPump is a subclassed version of PyPump which handles that for you. The only real difference is you don't specify a `verifier_callback` (if you do it will be ignored). Once the instanciation has completed you can guarantee that the URL for the callback has been created. Django ------ This is an example of a very basic django view which uses WebPump:: from pypump import WebPump from app.models import PumpModel from django.shortcuts import redirect from django.exceptions import ObjectDoesNotExist def pump_view(request, webfinger): try: webfinger = PumpModel.objects.get(webfinger=webfinger) except ObjectDoesNotExist: webfinger = PumpModel.objects.create(webfinger=webfinger) webfinger.save() # make the WebPump object if webfinger.oauth_credentials: pump = WebPump( webfinger.webfinger, client_type="web", client_name="DjangoApp", key=webfinger.key, secret=webfinger.secret token=webfinger.token, token_secret=token_secret, callback_uri="http://my_app.com/oauth/authorize" ) else: pump = WebPump( webfinger.webfinger, client_type="web", client_name="DjangoApp", callback_uri="http://my_app.com/oauth/authorize" ) # save the client credentials as they won't change webfinger.key, webfinger.secret, webfinger.expirey = pump.get_registeration() # save the request tokens so we can identify the authorize callback webfinger.token, webfinger.secret = pump.get_registrat() # save the model back to db webfinger.save() if pump.url is not None: # The user must go to this url and will get bounced back to our # callback_uri we specified above and add the webfinger as a # session cookie. request.session["webfinger"] = webfinger return redirect(pump.url) # okay oauth completed successfully, we can just save the oauth # credentials and redirect. webfinger.token, webfinger.token_secret = pump.get_registration() webfinger.save() # redirect to profile! return redirect("/profile/{webfinger}".format(webfinger)) def authorize_view(request): """ This is the redirect when authorization is complete """ webfinger = request.session.get("webfinger", None) token, verifier = request.GET["token"], request.GET["verifier"] try: webfinger = PumpModel.objects.get( webfiger=webfinger, token=token ) except ObjectDoesNotExist: return redirect("/error") # tell them this is a invalid request pump = WebPump( webfinger.webfinger, client_name="DjangoApp", client_type="web", key=pump.key, secret=pump.secret, ) pump.verifier(verifier) # Save the access tokens back now. webfinger.token, webfinger.token_secret = pump.get_registration() webfinger.save() # and redirect to their profile return redirect("/profile") python-pypump/docs/models/0000755000175000017500000000000012222046743014254 5ustar tagtagpython-pypump/docs/models/comment.rst0000644000175000017500000000412612222046743016453 0ustar tagtag Comment ======= This object represents pump.io's comments, these are posted on Notes and Images. .. note:: The pump variable is an instantiated PyPump class e.g. pump = PyPump("", client_name="", etc...) .. py:class:: Comment This represents a Comment object, These are used in reply to other others such as Images or Notes. .. py:attribute:: content This is the content of the Comment (string):: >>> my_comment = pump.Comment("This is a comment") >>> my_comment.content "This is a comment" .. py:attribute:: actor This is who posted the Comment (Person object):: >>> my_comment = pump.Comment("This is a comment") >>> my_comment.actor .. py:attribute:: updated This is when it was last updated (e.g. liked) this is a datetime.datetime object >>> my_comment.updated datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:attribute:: published This is when the Comment was first published, this is a datetime.datetime object >>> my_comment.published datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:method:: like() This likes/favourites a comment .. py:method:: unlike() This unlikes/unfavourites a comment .. py:method:: delete() This will delete the object. .. note:: favourite and unfavourite methods are mapped to their respective like and unlike methods. Example ------- This shows making a comment on a note:: >>> my_note >>> my_comment = pump.Comment("Oh hey, i'm commenting with PyPump!") # pump is instance of PyPump >>> my_note.comment(my_comment) You want to like the comment because you think PyPump is cool?:: >>> my_comment.like() What's that, you don't think PyPump is cool? :(:: >>> my_comment.unlike() And you want to delete your comment?:: >>> my_comment.delete() >>> my_comment = None .. warning: Using a deleted comment will cause DoesNotExist to be raised python-pypump/docs/models/note.rst0000644000175000017500000000454312222046743015761 0ustar tagtag Note ==== This object represents pump.io's notes, these are used to post text to a `person`'s inbox. .. note:: The pump variable is an instantiated PyPump class e.g. pump = PyPump("", client_name="", etc...) .. py:class:: Note This represents a note, short-form text message, these appear in the stream .. py:attribute:: content This is the content of the Note:: >>> my_note = pump.Note("I'm posting on pump.io") >>> my_note.content "I'm posting on pump.io" .. py:attribute:: actor This is who posted the Note (Person object):: >>> my_note = pump.Note("I'm posting on pump.io") >>> my_note.actor .. py:attribute:: updated This is when it was last updated (posted, commented, etc.) this is a datetime.datetime object >>> my_note.updated datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:attribute:: published This is when the Note was first published, this is a datetime.datetime object >>> my_note.published datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:attribute:: to This is a list of person objects, who the Note was to:: >>> my_note.to [, ] .. py:attribute:: cc This is a list of person objects, who was carbon copied into the Note:: >>> my_note.cc [] .. py:method:: comment(comment) This takes in a Comment object, this will send the comment to the remote server. .. py:method:: delete() This will delete the object. Example ------- This shows making a new post:: >>> my_note = pump.Note("This is a new note!") # pump is instance of PyPump You want to comment?:: >>> my_comment = pump.Comment("I'm commenting on my note") >>> my_note.comment(my_comment) You can like the note:: >>> my_comment.like() Oh wait? you didn't want to...:: >>> my_comment.unlike() Oh you didn't want to post the note?:: >>> my_comment.delete() >>> my_comment = None # we don't want to accidently try and use a deleted comment .. warning:: Using a comment object after deletion will raise a DoesNotExist exception python-pypump/docs/models/activity.rst0000644000175000017500000000363012222046743016644 0ustar tagtag Activity ==== This object represents pump.io's activities, these are used in feeds like `inbox`. .. note:: The pump variable is an instantiated PyPump class e.g. pump = PyPump("", client_name="", etc...) .. py:class:: Activity This represents an activity:: >>>me = pump.Person("kabniel@microca.st") >>>my_inbox = me.inbox[:10] >>>my_activity = my_inbox[0] .. py:attribute:: id This is the id of the activity:: >>> my_activity.id "https://e14n.com/api/activity/Y7Lxll6jSGSqTjFT5dbaqQ" .. py:attribute:: url This is the url of the activity:: >>> my_activity.url 'https://e14n.com/evan/activity/Y7Lxll6jSGSqTjFT5dbaqQ' .. py:attribute:: content This is the content of the activity:: >>> my_activity.content "Evan Prodromou posted a note" .. py:attribute:: actor This is who posted the activity (Person object):: >>> my_activity.actor .. py:attribute:: updated This is when the activity was last updated. this is a datetime.datetime object >>> my_activity.updated datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:attribute:: published This is when the activity was first published, this is a datetime.datetime object >>> my_activity.published datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:attribute:: generator This is the client that generated the activity:: >>> my_activity.generator .. py:obj:: obj This is the object that was affected by the activity:: >>> my.activity.obj Example ------- No examples yet! python-pypump/docs/models/person.rst0000644000175000017500000000574712222046743016331 0ustar tagtag Person ======= This object represents a user on pump.io. .. note:: The pump variable is an instantiated PyPump class e.g. pump = PyPump("", client_name="", etc...) .. py:class:: Person This represents a Person object, These are used in getting their inboxes, information on, etc... .. py:attribute:: preferred_username This is the username (you should display the display_name) (unicode):: >>> a_person.preferred_username u'Tsyesika' .. py:attribute:: display_name This is the username you should display (unicode):: >>> a_person.display_name u'Tsyesìka' .. py:attribute:: updated This is when their profile was updated (datetime.datetime):: >>> a_person.updated datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:attribute:: published This is when their profile was first published (datetime.datetime):: >>> a_person.published datetime.datetime(2013, 6, 15, 12, 31, 22, 134180) .. py:attribute:: url The URL to the profile (string):: >>> a_person.url 'https://pump.megworld.co.uk/Tsyesika' .. py:attribute:: followers This is a list of their followers (list):: >>> a_person.followers [, , .. py:attribute:: following This is a list of all the people they're following:: >>> a_person.following [] .. py:attribute:: location This is the location of the user (Location object):: >>> a_person.location .. py:attribute:: summary This is the summer of the user (unicode):: >>> a_person.summary u'The maker of this fabulous library!' .. py:attribute:: image This is the image of the person (Image object):: >>> a_person.image .. py:method:: follow This will follow the user if you're not already following them .. py:method:: unfollow This will stop following the user if you were following them Example ------- This shows how to follow someone:: >>> a_person.follow() # yay we're now following them! What happens when i try and follow someone I am?:: >>> a_person.follow() # nothing? yep. Well, I don't want to follow them:: >>> a_person.unfollow() # awhh :( You want to find yourfriend@pumpity.net:: >>> my_friend = pump.Person("yourfriend@pumpity.net") Traceback (most recent call last): blah blah you know the drill DoesNotExist: Can't find yourfriend@pumpity.net Oh that's right they're on pump.megworld.co.uk:: >>> my_friend = pump.Person("myfriend@pump.megworld.co.uk") >>> # Awesome! python-pypump/shell0000755000175000017500000001636312222046743013107 0ustar tagtag#!/usr/bin/env python ## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## import json import sys import os from pypump import PyPump try: from IPython.frontend.terminal.embed import InteractiveShellEmbed from IPython.config.loader import Config except ImportError: print("You need to have ipython installed to run this") sys.exit(1) store_path = os.path.expanduser("~/.pypump_test") welcome_banner = """Welcome to the PyPump testing shell, We have setup some useful objects for you: {table} If you need help please visit our docs: https://pypump.readthedocs.org/en/latest Enjoy! """ def ascii_table(headings, data): """ Draws an ascii table """ # first we need to work out how long each column should be columns = {} for heading in headings: columns[heading] = len(heading)+1 # plus one for the extra padding # data could also overspill for record in data: for heading, d in record.items(): dlen = len(d)+1 if dlen > columns[heading]: columns[heading] = dlen table = {} # okay now we need to pad the headers readying for drawing for column, width in columns.items(): table[column] = "{name}{padding}".format( name=column, padding=" "*(width-len(column)) ) heading = "| " for column in table.values(): heading += column heading += "| " # now for the data table = {} for i, record in enumerate(data): table[i] = "| " for column, cdata in record.items(): table[i] += "{data}{padding} | ".format( data=cdata, padding=" "*(columns[column]-len(cdata)-1) ) # make the helper function which will draw the seporators def draw_sep(columns): """ Draws +----+--- etc... """ sep = "+" for width in columns: sep += "-"*(width+1) sep += "+" return sep + "\r\n" sepper = lambda :draw_sep(columns.values()) stable = sepper() stable += heading + "\r\n" stable += sepper() for value in table.values(): stable += value + "\r\n" stable += sepper() return stable # few, glad that's over def open_database(path): if os.path.exists(path): try: fin = open(path, "r") db = json.loads(fin.read()) return db except Exception: print("There has been a problem parsing the database {0}".format(path)) sys.exit(1) else: return {} def save_database(db, path): """ Saves db as JSON to path """ if os.path.exists(path): try: os.remove(path) except Exception: print("There was an error removing database from {0}".format(path)) try: fout = open(path, "w") db = json.dumps(db, sort_keys=True, indent=4, separators=(",", ": ")) fout.write(db) fout.close() except Exception: raise print("There was an error saving the database to {0}".format(path)) return db if __name__ == "__main__": if "-h" in sys.argv or "--help" in sys.argv: print("Usage:") print(" {0} [client_name]".format(sys.argv[0])) sys.exit(0) if "-v" in sys.argv or "--version" in sys.argv: if os.path.exists("VERSION"): print(open("VERSION", "r").read()) else: print("Unknown.") sys.exit(0) if "--clean" in sys.argv: if not os.path.exists(store_path): print("Nothing to do.") sys.exit(0) try: os.remove(store_path) print("Removed all files used by PyPump tester") except Exception: print("Something went wrong, probably permissions") print("The file I should have been writing to is {0}".format(store_path)) sys.exit(0) # go find the client info if it exists db = open_database(store_path) webfinger = "" if len(sys.argv) <= 1 else sys.argv[1] if db == dict() and webfinger is None: print("You must specify a webfinger") sys.exit(1) for wf in db.keys(): if webfinger in wf: webfinger = wf # okay so. if webfinger.find("@") == -1: print("Can't find webfinger {0}".format(webfinger)) if webfinger not in db: if len(sys.argv) <= 2: client_name = "PyPump" else: client_name = sys.argv[2] db[webfinger] = { "client_name": client_name } else: client_name = db[webfinger].get("client_name", "PyPump") db[webfinger]["client_name"] = client_name sys.stdout.write("-> Setting up PyPump ") sys.stdout.flush() # actually get it on the screen - most terms wait for \n pump = PyPump( webfinger, client_name=client_name, key=db[webfinger].get("key", None), secret=db[webfinger].get("secret", None), client_type=db[webfinger].get("client_type", "native"), token=db[webfinger].get("token", None), token_secret=db[webfinger].get("token_secret", None), ) client = pump.get_registration() db[webfinger]["key"] = client[0] db[webfinger]["secret"] = client[1] db[webfinger]["expirey"] = client[2] tokens = pump.get_token() db[webfinger]["token"] = tokens[0] db[webfinger]["token_secret"] = tokens[1] save_database(db, store_path) # setup other variables for user. me = pump.Person(webfinger) # bring curser back so banner walks over the setup message sys.stdout.write("\r") # drop them into a shell cfg = Config() cfg.InteractiveShell.confirm_exit = False # prep the welcome banner welcome_banner = welcome_banner.format( table=ascii_table( ["Variable", "Representation", "Type"], [ { "Variable": "pump", "Representation": str(repr(pump)), "Type": type(pump).__name__, }, { "Variable": "me", "Representation": str(repr(me)), "Type": type(me).__name__, } ] ) ) shell = InteractiveShellEmbed( config=cfg, banner1=welcome_banner, exit_msg="Remeber! Report any bugs to https://github.com/xray7224/PyPump/issues" ) shell() python-pypump/.gitignore0000644000175000017500000000037612222046743014037 0ustar tagtag*.py[cod] *__pycache__* .*.swp # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg python-pypump/AUTHORS.md0000644000175000017500000000073612222046743013516 0ustar tagtagThank you ========== I want to thank anyone who has contributed, I have limited time myself so this project needs other people. I decided to start this list just to identify contributors and thank them. Programming Contributors ========================= - Christopher Webber / cwebber (https://github.com/cwebber) - Sibi / psibi (https://github.com/psibi) - Jonas Haraldsson / kabniel (https://github.com/kabniel) - Benjamin Ooghe-Tabanou / RouxRC (https://github.com/RouxRC) python-pypump/README.rst0000644000175000017500000000352012222046743013530 0ustar tagtag================================ PyPump - Python Pump.io Library ================================ :Version: 0.3 :Web: https://github.com/xray7224/PyPump/ :Keywords: pump.io, library, social, federated What is Pump.io? ============== Pump.io is a socially oriented federated network. It provides a way to post notes and images as well and subscribe to other people to get updates on what they post. What's PyPump? =============== PyPump provides an interface to the pump.io API's. The aim is to provide very natural pythonic representations of Notes, Images, People, etc... allowing you to painlessly interact with them. What does PyPump require? ========================== PyPump works with: - Python (2.6, 2.7, 3.3+) - requests-oauthlib 0.3.0+ - requests 1.2.0+ *We provide a unified version which works with both the python 2 and python 3 versions.* Documentation ============= PyPump is documented. They are currently hosted on Read The Docs: `latest docs `_ Installation ============ You can install it via pip:: $ pip install pypump Chat with us? ============= If you've found an issue please report it on the GitHub issue's https://github.com/xray7224/PyPump/issues If you just want to chat and let us know the cool things you're doing with PyPump (or want to do) then we have an IRC channel, we currently have our channel on MegNet: .. _`MegNet`: https://megworld.co.uk/irc .. _`Webchat`: https://webchat.megworld.co.uk/?channels=#pypump Licence? ======== We're licensed under the GPL version 3 or (at your option) a later version. You can find more information about this licence at https://www.gnu.org/licenses/gpl.html Want to help out? ================== Fantastic! Please do, we always need developers, translators (for documentation), testers, etc... Head over to the github and IRC! python-pypump/run_tests0000755000175000017500000000025212222046743014014 0ustar tagtag#!/usr/bin/bash TEST_DIR="tests/" echo "Running PyPump tests!" if [ -e $TEST_DIR ]; then nosetests -w $TEST_DIR else echo $TEST_DIR." could not be found." fi python-pypump/setup.py0000644000175000017500000000323412222046743013555 0ustar tagtagtry: from setuptools import setup except ImportError: from distutils.core import setup setup( name="PyPump", version="0.3", description="Python Pump.io library", long_description=open("README.rst").read(), author="Jessica Tallon", author_email="tfmyz@inboxen.org", url="https://github.com/xray7224/PyPump", packages=["pypump", "pypump.exception", "pypump.models"], license="GPLv3", install_requires=[ "requests-oauthlib>=0.3.0", "requests>=1.2.0", "nose", "python-dateutil>=2.1", ], classifiers=[ "Development Status :: 3 - Alpha", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: Implementation :: CPython", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Operating System :: BeOS", "Intended Audience :: Developers", "License :: OSI Approved", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", ] ) python-pypump/pypump/0000755000175000017500000000000012222046774013377 5ustar tagtagpython-pypump/pypump/openid.py0000644000175000017500000000633112222046743015226 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## import json import requests from compatability import * class OpenIDException(Exception): pass class Consumer(object): """ Container for holding the client registration details """ key = None secret = None expirey = 0 # never def __repr__(self): return "" % self.key def __str__(self): return self.__repr__() class OpenID(object): ENDPOINT = "api/client/register" pypump = None server = None request = None type = None logo_url = None consumer = None def __init__(self, server, client_name, application_type, logo_url=None): # we don't want the webfinger just the server if "@" in server: self.server = server.split("@", 1)[1] else: self.server = server self.name = client_name self.type = application_type self.logo_url = self.logo_url if logo_url is not None else logo_url def register_client(self): """ Sends a client registration request """ data = { "type":"client_associate", "application_name":self.name, "application_type":self.type, } if self.logo_url: data["logo_url"] = self.logo_url data = json.dumps(data) if self.server is None: raise OpenIDException("Server must be set") request = { "headers": {"Content-Type": "application/json"}, "data": data } if self.server == self.pypump.server: response = self.pypump._requester(requests.post, self.ENDPOINT, **request) else: url = "{proto}://{server}/{endpoint}".format( proto=self.pypump.protocol, server = self.server, endpoint = self.ENDPOINT ) response = self.pypump._requester(requests.post, url, **request) try: server_data = response.json() except ValueError: raise OpenIDException(response.content) if "error" in server_data: raise OpenIDException(server_data["error"]) consumer = Consumer() consumer.key = server_data["client_id"] consumer.secret = server_data["client_secret"] consumer.expirey = server_data["expires_at"] self.consumer = consumer return consumer def __repr__(self): return "" % self.server def __str__(self): return self.__repr__() python-pypump/pypump/exception/0000755000175000017500000000000012222046743015371 5ustar tagtagpython-pypump/pypump/exception/ServerError.py0000644000175000017500000000031112222046743020216 0ustar tagtagimport json class ServerError(Exception): def __init__(self, data, *args, **kwargs): data = json.loads(data) super(ServerError, self).__init__(data["error"], *args, **kwargs) python-pypump/pypump/exception/__init__.py0000644000175000017500000000005312222046743017500 0ustar tagtagclass PyPumpException(Exception): pass python-pypump/pypump/exception/DoesNotExist.py0000644000175000017500000000005212222046743020330 0ustar tagtag class DoesNotExist(Exception): pass python-pypump/pypump/exception/ImmutableException.py0000644000175000017500000000042512222046743021542 0ustar tagtag class ImmutableException(Exception): message = "You can't set %s on %s, the object is immutable." def __init__(self, item, obj, *args, **kwargs): message = self.message % (item, obj) super(ImmutableException, self).__init__(message, *args, **kwargs) python-pypump/pypump/exception/PumpException.py0000644000175000017500000000015112222046743020540 0ustar tagtag class PumpException(Exception): """ This is used when the remote server gives an error """ pass python-pypump/pypump/compatability.py0000644000175000017500000000710612222046743016612 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## """ This module is used to provide some python 2.6 to python 3.3 and above compatibility. This has been largely helped by the information at: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ """ import sys import types # establish which version we're using py_version = sys.version_info[0] # handing strings if py_version == 2: def to_bytes(string): """ Converts a python 2 string to bytes """ if type(string) is types.StringType: return string # is it unicode? if type(string) is types.UnicodeType: return string.encode("utf-8") raise TypeError("Unknown type %s" % type(string)) def to_unicode(string): """ Convert to unicode object """ if type(string) is types.StringType: return string.decode("utf-8") if type(string) is types.UnicodeType: return string raise TypeError("Unknown type %s" % type(string)) elif py_version == 3: def to_bytes(string): """ Converts a python 3 string to bytes """ if type(string) is bytes: return string if type(string) is str: return string.encode("utf-8") def to_unicode(string): """ Convert to unicode object """ if type(string) is bytes: return string.decode("utf-8") if types(string) is str: return string raise TypeError("Unknown type %s" % string) else: def to_bytes(string): """ not implemented """ raise NotImplemented("Python version %s has no support for to_bytes" % py_version) # types for isinstance(, ) if py_version == 2: text_type = str string_types = (str, unicode) elif py_version == 3: text_type = str string_types = (str, bytes) # add decorators if py_version == 2: def implement_to_string(cls): """ Adds to __str__ and __unicode__ methods when existing __str__ exists """ cls.__unicode__ = cls.__str__ cls.__str__ = lambda s: s.__unicode__().encode("utf-8") return cls elif py_version == 3: def implement_to_string(cls): """ Adds __bytes__ to a clss with __str__ """ cls.__bytes__ = lambda s: s.__str__().encode("utf-8") return cls else: def implement_to_string(cls): raise NotImplemented("Python version %s has no support for implement_to_string" % py_version) # is class? def is_class(cls): """ Return bool depending on if cls is a class """ return isinstance(cls, type) # handles urllib change if py_version == 2: import cgi parse_qs = cgi.parse_qs elif py_version == 3: import urllib.parse parse_qs = urllib.parse.parse_qs else: def parse_qs(*args, **kwargs): raise NotImplemented("Python version %s has no support for parse_qs" % py_version) # raw_input vs. input if py_version == 3: raw_input = input python-pypump/pypump/__init__.py0000644000175000017500000000012212222046743015477 0ustar tagtagfrom __future__ import absolute_import from pypump.pypump import PyPump, WebPump python-pypump/pypump/pypump.py0000644000175000017500000003331712222046774015312 0ustar tagtag# -*- coding: utf-8 -*- ## # Copyright (C) 2010-2012 Reality and Psychedelic Squid # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from __future__ import absolute_import import json import requests from requests_oauthlib import OAuth1 import pypump.openid as openid from pypump.compatability import * from pypump.exception import PyPumpException # load models from pypump.models.note import Note from pypump.models.comment import Comment from pypump.models.person import Person from pypump.models.image import Image from pypump.models.inbox import Inbox from pypump.models.outbox import Outbox from pypump.models.location import Location from pypump.models.list import List, Public, Followers, Following from pypump.models.activity import Activity class PyPump(object): PARAM_VERIFER = to_bytes("oauth_verifier") PARAM_TOKEN = to_bytes("oauth_token") PARAM_TOKEN_SECRET = to_bytes("oauth_token_secret") URL_CLIENT_REGISTRATION = "/api/client/register" loader = None protocol = "https" client = None def __init__(self, server, key=None, secret=None, client_name="", client_type="native", token=None, token_secret=None, verifier_callback=None, callback_uri="oob"): """ This is the main pump instance, this handles the oauth, this also holds the models. Don't forget if you want to use https ensure the secure flag is True """ openid.OpenID.pypump = self # pypump uses PyPump.requester. self.verifier_callback = verifier_callback self.client_name = client_name self.client_type = client_type self.callback_uri = callback_uri if "@" in server: # it's a web fingerprint! self.nickname, self.server = server.split("@") else: self.server = server self.nickname = None # should be set with .set_nickname() # Fix #24 by checking if (key is None or secret is None) and (token or token_secret): raise Exception("If token and/or token_secret are supplied you must supply key and secret too") self.populate_models() # first, if we need to register our client if not (key or secret): oid = openid.OpenID( server=server, client_name=client_name, application_type=client_type ) self.consumer = oid.register_client() else: self.consumer = openid.Consumer() self.consumer.key = key self.consumer.secret = secret if not (token and token_secret): # we need to make a new oauth request self.oauth_request() # this does NOT return access tokens but None else: self.token = token self.token_secret = token_secret def populate_models(self): # todo: change me self.Note = Note self.Note._pump = self self.Comment = Comment self.Comment._pump = self self.Inbox = Inbox self.Inbox._pump = self self.Outbox = Outbox self.Outbox._pump = self self.Image = Image self.Image._pump = self self.Person = Person self.Person._pump = self self.Location = Location self.Location._pump = self self.List = List self.List._pump = self self.Public = Public self.Public._pump = self self.Following = Following self.Following._pump = self self.Followers = Followers self.Followers._pump = self self.Activity = Activity self.Activity._pump = self ## # getters to expose some data which might be useful ## def get_registration(self): """ This is if key and secret weren't specified at instansiation so we registered them """ return (self.consumer.key, self.consumer.secret, self.consumer.expirey) def get_token(self): """ This is for when we don't have a token but we've registered one (by asking the user) """ return (self.token, self.token_secret) def set_nickname(self, nickname): """ This sets the nickname being used """ if nickname: self.nickname = str(nickname) # everything in python can be converted to a string right? else: # they didn't enter a nickname? raise Exception("Nickname can't be of length 0") ## # server ## def build_url(self, endpoint): """ Returns a fully qualified URL """ server = None if "://" in endpoint: #looks like an url, let's break it down server, endpoint = self.deconstruct_url(endpoint) endpoint = endpoint.lstrip("/") url = "{proto}://{server}/{endpoint}".format( proto=self.protocol, server=self.server if server is None else server, endpoint=endpoint ) return url def deconstruct_url(self, url): """ Breaks down URL and returns server and endpoint """ proto, url = url.split("://") server, endpoint = url.split("/", 1) return (server, endpoint) def request(self, endpoint, method="GET", data="", raw=False, params=None, attempts=10, client=None): """ This will make a request to :/// with oauth headers method = GET (default), POST or PUT attempts = this is how many times it'll try re-attempting """ if client is None: client = self.client # check client has been setup if client is None: self.setup_oauth_client() client = self.client params = {} if params is None else params if endpoint.startswith("/"): endpoint = endpoint[1:] # remove inital / as we add it if data and isinstance(data, dict): # we actually need to make it into a json object as that's what pump.io deals with. data = json.dumps(data) data = to_unicode(data) if not raw: url = self.build_url(endpoint) else: url = endpoint lastresponse = "" for attempt in range(attempts): if method == "POST": request = { "auth": client, "headers": {"Content-Type": "application/json"}, "params": params, "data": data, } response = self._requester(requests.post, endpoint, raw, **request) elif method == "GET": request = { "params": params, "auth": client, } response = self._requester(requests.get, endpoint, raw, **request) if response.status_code == 200: # huray! return response.json() ## # Debugging ## if response.content != lastresponse: lastresponse = response.content print response print response.content if response.status_code == 400: # can't do much try: try: data = response.json() error = data["error"] except ValueError: error = response.content if not error: raise IndexError # yesss i know. except IndexError: error = "Received a 400 bad request error. This is likely due to an OAuth failure" raise PyPumpException(error) # failed, oh no! error = "Failed to make request to {0} ({1} {2})".format(url, method, data) raise PyPumpException(error) def _requester(self, fnc, endpoint, raw=False, **kwargs): if not raw: url = self.build_url(endpoint) else: url = endpoint try: response = fnc(url, **kwargs) return response except requests.exceptions.ConnectionError: if self.protocol == "http" or raw: raise # shoot this seems real. else: # rebuild url using http for raw request then go back to https as default self.set_http() url = self.build_url(endpoint) self.set_https() raw = True return self._requester(fnc, url, raw, **kwargs) def set_https(self): """ Enforces protocol to be https """ self.protocol = "https" def set_http(self): """ Sets protocol to be http """ self.protocol = "http" ## # OAuth specific stuff ## def oauth_request(self): """ Makes a oauth connection """ # get tokens from server and make a dict of them. self.__server_tokens = self.request_token() self.token = self.__server_tokens["token"] self.token_secret = self.__server_tokens["token_secret"] url = self.build_url("oauth/authorize?oauth_token={token}".format( protocol=self.protocol, server=self.server, token=self.token.decode("utf-8") )) # now we need the user to authorize me to use their pump.io account if self.verifier_callback is None: verifier = self.get_access(url) self.verifier(verifier) else: self.verifier_callback(url) def verifier(self, verifier): """ Called once verifier has been retrived """ self.__server_tokens["verifier"] = verifier self.request_access(**self.__server_tokens) def setup_oauth_client(self): """ Sets up client for requests to pump """ self.client = OAuth1( client_key=to_unicode(self.consumer.key), client_secret=to_unicode(self.consumer.secret), resource_owner_key=to_unicode(self.token), resource_owner_secret=to_unicode(self.token_secret) ) def get_access(self, url): """ this asks the user to let us use their account """ print("To allow us to use your pump.io please follow the instructions at:") print(url) code = raw_input("Verifier Code: ").lstrip(" ").rstrip(" ") return code def request_token(self): """ Gets a request token so that we can then ask the user for access to the accoutn """ client = OAuth1( client_key=self.consumer.key, client_secret=self.consumer.secret, callback_uri=self.callback_uri ) request = {"auth": client} response = self._requester(requests.post, "oauth/request_token", **request) data = parse_qs(response.content) data = { 'token': data[self.PARAM_TOKEN][0], 'token_secret': data[self.PARAM_TOKEN_SECRET][0] } return data def request_access(self, **auth_info): """ This is for when we've got the user's access value and we're asking the server for our access token """ client = OAuth1( client_key=self.consumer.key, client_secret=self.consumer.secret, resource_owner_key=auth_info['token'], resource_owner_secret=auth_info['token_secret'], verifier=auth_info['verifier'] ) request = {"auth": client} response = self._requester(requests.post, "oauth/access_token", **request) data = parse_qs(response.content) self.token = data[self.PARAM_TOKEN][0] self.token_secret = data[self.PARAM_TOKEN_SECRET][0] self.__server_tokens = None # clean up code. class WebPump(PyPump): """ This is a PyPump class which is aimed at mainly web developers. Allowing you to avoid the callbacks making the oauth portion of PyPump instanciation blocking. After initialisation you will be able to do `PyPump.verifier_url` allowing you to get the url to direct your user to. That method will return None if the oauth handshake was successful and no verifier callback needs to be done. Once you have the verifier instanciate this class again and call the verifier method alike what you do using the PyPump class """ url = None def __init__(self, *args, **kwargs): """ This is exactly the same as PyPump.__init__ apart from verifier_callback is no longer an option for kwargs and if specified will be ignored. """ kwargs["verifier_callback"] = self._callback_verifier super(WebPump, self).__init__(*args, **kwargs) def _callback_verifier(self, url): """ This is used to catch the url and store it at `self.url` """ self.url = url python-pypump/pypump/models/0000755000175000017500000000000012222046774014662 5ustar tagtagpython-pypump/pypump/models/location.py0000644000175000017500000000445712222046743017052 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from pypump.models import AbstractModel from pypump.compatability import * @implement_to_string class Location(AbstractModel): name = None longitude = None latitude = None def __init__(self, name, longitude, latitude, *args, **kwargs): super(Location, self).__init__(*args, **kwargs) self.name = name self.longitude = longitude self.latitude = latitude def __repr__(self): return "<{type} {name}>".format(type=self.TYPE, name=self.name) def __str__(self): return str(self.__repr__()) @classmethod def unserialize(cls, data, obj=None): name = data.get("displayName", None) if ("lon" in data and "lat" in data): longitude = float(data["lon"]) latitude = float(data["lat"]) elif "position" in data: position = data["position"][:-1] if position[1:].find("+") != -1: latitude = position.lstrip("+").split("+", 1)[0] latitude = float(latitude) longitude = float(position[1:].split("+", 1)[1]) else: latitude = position.lstrip("+").split("-", 1)[0] latitude = float(latitude) longitude = float(position[1:].split("-", 1)[1]) else: longitude = None latitude = None if obj is None: return cls(name=name, longitude=longitude, latitude=latitude) else: obj.name = name obj.longitude = longitude obj.latitude = latitude return obj python-pypump/pypump/models/inbox.py0000644000175000017500000000206412222046774016355 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from pypump.compatability import * from pypump.models.feed import Feed class Inbox(Feed): OBJECT_TYPES = "Activity" @property def ENDPOINT(self): if self._ENDPOINT: return self._ENDPOINT self._ENDPOINT = "api/user/{username}/inbox".format(username=self.actor.username) return self.ENDPOINT python-pypump/pypump/models/image.py0000644000175000017500000000627012222046743016317 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## import datetime from dateutil.parser import parse from pypump.compatability import * from pypump.models import (AbstractModel, Likeable, Commentable, Deleteable, Shareable) @implement_to_string class Image(AbstractModel, Likeable, Shareable, Commentable, Deleteable): url = None actor = None author = actor summary = "" id = None updated = None published = None _links = None @property def ENDPOINT(self): return "/api/user/{username}/feed".format(self._pump.nickname) def __init__(self, id, url, content=None, actor=None, width=None, height=None, published=None, updated=None, links=None, *args, **kwargs): super(Image, self).__init__(self, *args, **kwargs) self.id = id self.content = content self.actor = self.actor if actor is None else actor self.url = url self.published = published self.updated = updated self._links = dict() if links is None else links def __repr__(self): return "<{type} by {webfinger}>".format( type=self.TYPE, url=self.actor.webfinger) def __str__(self): return str(repr(self)) @classmethod def unserialize(cls, data, obj=None): image_id = data["id"] if "fullImage" in data: full_image = data["fullImage"]["url"] full_image = cls(id=image_id, url=full_image) else: full_image = None if "image" in data: image = data["image"]["url"] image = cls(id=image_id, url=image) else: image = None author = cls._pump.Person.unserialize(data["author"]) links = dict() for i in ["likes", "replies", "shares"]: if data.get(i, None): if "pump_io" in data[i]: links[i] = data[i]["pump_io"]["proxyURL"] else: links[i] = data[i]["url"] for i in [full_image, image]: i.actor = author i.published = parse(data["published"]) i.updated = parse(data["updated"]) i.display_name = data.get("displayName", str()) # set the full and normal image on each one full_image.image = image full_image.original = full_image image.image = image image.original = full_image # and finally the links full_image._links = links image._links = links return image python-pypump/pypump/models/activity.py0000644000175000017500000001050712222046743017067 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from dateutil.parser import parse as parse import re from pypump.models import AbstractModel from pypump.compatability import * #TODO clean up and move to own file class Generator(object): """ The client used to generate the activity """ display_name = None id = None def __repr__(self): return "".format(g_name=self.display_name) def __init__(self, display_name=display_name, id=id, *args, **kwargs): self.display_name = display_name self.id = id super(Generator, self).__init__(*args, **kwargs) @classmethod def unserialize(cls, data): id = data["id"] display_name = data["displayName"] if "displayName" in data else "Unknown client" return cls(id=id, display_name=display_name) #TODO clean up and move to own file @implement_to_string class Unknown(AbstractModel): """ This class is used when we can't find a matching object type """ TYPE = None display_name = None def __repr__(self): return self.display_name if self.display_name else self.TYPE def __str__(self): return str(self.__repr__()) def __init__(self, object_type=None, display_name=None, *args, **kwargs): self.TYPE = object_type self.display_name = display_name super(Unknown, self).__init__(*args, **kwargs) @classmethod def unserialize(cls, data): object_type = data["objectType"] if "objectType" in data else "" display_name = data["displayName"] if "displayName" in data else "" return cls(display_name=display_name, object_type=object_type) @implement_to_string class Activity(AbstractModel): obj = None verb = None actor = None generator = None updated = None url = None published = None content = None id = None def __init__(self, obj, verb, actor, generator, updated, url, published, content, id, *args, **kwargs): super(Activity, self).__init__(*args, **kwargs) self.obj = obj self.verb = verb self.actor = actor self.generator = generator self.updated = updated self.url = url self.published = published self.content = content self.id = id def __repr__(self): return ''.format( webfinger=self.actor.id[5:], # [5:] to strip of acct: verb=self.verb.rstrip("e"), # english: e + ed = ed model=self.obj.objectType ) def __str__(self): return str(self.__repr__()) @classmethod def unserialize(cls, data): """ From JSON -> Activity object """ dataobj = data["object"] obj_type = dataobj["objectType"].capitalize() if "author" not in dataobj: # author is not set for posted objects in inbox/major, so we add it dataobj["author"] = data["actor"] try: objekt = getattr(cls._pump, obj_type) obj = objekt.unserialize(dataobj) except AttributeError: obj = Unknown.unserialize(dataobj) verb = data["verb"] actor = cls._pump.Person.unserialize(data["actor"]) # generator is not always there (at least not for verb:'update' obj:Person) generator = Generator.unserialize(data["generator"]) if "generator" in data else None updated = parse(data["updated"]) url = data["url"] published = parse(data["published"]) content = data["content"] id = data["id"] return cls(obj, verb, actor, generator, updated, url, published, content, id) python-pypump/pypump/models/feed.py0000644000175000017500000001425512222046774016146 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from pypump.compatability import * from pypump.exception import PyPumpException from pypump.models import AbstractModel class InfiniteFeed(object): previous = None def __init__(self, feed, offset=None, count=20, stop=None, step=1): self.feed = feed self.count = count self.maxcount = 200 # upper API limit for requested items self.offset = offset self.cache = list() self.counter = 0 self.stop = stop self.step = step def __iter__(self): return self def next(self): if not self.cache: if self.previous is None: # first iteration response = self.feed._request(count=self.count, offset=self.offset) else: response = self.feed._request(count=self.count, before=self.previous) self.cache = response['items'] data = self.cache.pop(0) if self.cache else None if not data or (self.stop and self.counter >= self.stop): # we're done raise StopIteration if self.counter > self.count: # raise count if we iterate a lot if self.counter < self.maxcount: self.count = self.counter else: self.count = self.maxcount obj = getattr(self.feed._pump, self.feed.OBJECT_TYPES) obj = obj.unserialize(data) self.previous = obj.id self.counter += 1 if (self.counter-1) % self.step == 0: return obj else: return self.next() @implement_to_string class Feed(AbstractModel): _count = None _offset = None actor = None author = actor _ENDPOINT = None @property def ENDPOINT(self): if self._ENDPOINT is None: raise NotImplemented("Definition of the ENDPOINT must be done by subclass") return self._ENDPOINT @property def major(self): endpoint = self.ENDPOINT if not endpoint.endswith("/"): endpoint += "/" endpoint += "major" return self.__class__(username=self.actor, endpoint=endpoint) @property def minor(self): endpoint = self.ENDPOINT if not endpoint.endswith("/"): endpoint += "/" endpoint += "minor" return self.__class__(username=self.actor, endpoint=endpoint) def __init__(self, username=None, endpoint=None): if endpoint is not None: self._ENDPOINT = endpoint if isinstance(username, self._pump.Person): self.actor = username return self.actor = self._pump.Person(username) def __getitem__(self, key): """ Adds Feed[] """ if isinstance(key, slice): return self.__getslice__(key) count = 1 offset = key inf = InfiniteFeed(self, offset=offset, count=count, stop=count) return inf.next() def __getslice__(self, s, e=None): """ Grab multiple items from feed """ if type(s) is not slice: s = slice(s,e) if s.start and s.stop: count = s.stop - s.start offset = s.start elif s.stop: count = s.stop offset = None elif s.start: offset = s.start count = None if s.step: step = s.step else: step = 1 return InfiniteFeed(self, offset=offset, stop=count, step=step) def __iter__(self): """ Produces an iterator """ return InfiniteFeed(self) def __repr__(self): if self.author: return "<{type}: {actor}>".format( actor=self.actor, type=self.TYPE, ) else: return "<{type}>".format(type=self.TYPE) def __str__(self): return str(repr(self)) def _request(self, offset=None, count=None, since=None, before=None): """ Makes a request """ param = dict() if count is not None: param["count"] = count if offset is not None: param["offset"] = offset if since is not None: param["since"] = before elif before is not None: param["before"] = before if self.actor is None: # oh dear, we gotta raise an error raise PyPumpException("No actor defined on {feed}".format(feed=self)) data = self._pump.request(self.ENDPOINT, params=param) return data @classmethod def unserialize(cls, data, user=None): """ Produce a List from JOSN data """ if type(data) == list: items = data elif type(data) == dict: items = data["items"] else: raise Exception("Unknown type: {type} ('{data}')".format( type=type(data), data=data )) unserialized_items = list() for v in items: # do we know about it? obj_type = cls.OBJECT_TYPES # todo: make less hacky try: obj = getattr(cls._pump, obj_type) except AttributeError: continue # what is this stange type of which you are? try: real_obj = obj.unserialize(v) if real_obj is not None: unserialized_items.append(real_obj) except TypeError: pass return unserialized_items python-pypump/pypump/models/note.py0000644000175000017500000001445012222046774016205 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from datetime import datetime from dateutil.parser import parse from pypump.exception.ImmutableException import ImmutableException from pypump.exception.PumpException import PumpException from pypump.compatability import * from pypump.models import (AbstractModel, Likeable, Shareable, Commentable, Deleteable) @implement_to_string class Note(AbstractModel, Likeable, Shareable, Commentable, Deleteable): VERB = "post" @property def ENDPOINT(self): return "/api/user/{username}/feed".format( username=self._pump.nickname ) id = "" content = "" updated = None # last time this was updated published = None # When this was published deleted = False # has the note been deleted liked = False author = None # where to? _to = list() _cc = list() _links = dict() def __init__(self, content, id=None, to=None, cc=None, published=None, updated=None, links=None, deleted=False, liked=False, author=None, *args, **kwargs): super(Note, self).__init__(*args, **kwargs) self._links = links if links else dict() self.id = id if id else None self.content = content self._to = list() if to is None else to self._cc = list() if cc is None else cc if published: self.published = published else: self.published = datetime.now() if updated: self.updated = updated else: self.updated = self.published self.deleted = deleted self.liked = liked self.author = self._pump.Person(self._pump.nickname) if author is None else author def set_to(self, people): """ Allows you to set/change who it's to """ # check if it's been locked if isinstance(self._to, tuple): raise ImmutableError("people", self) if type(people) == tuple: people = list(people) if type(people) != list: people = [people] for i, item in enumerate(people): if is_class(item): people[i] = item() if isinstance(people[i], self._pump.Person): people[i] = { "id": people[i].id, "objectType": people[i].objectType, } else: # must be collection/list people[i] = people[i].serialize(as_dict=True) self._to = people def get_to(self): return self._to to = property(fset=set_to, fget=get_to) def set_cc(self, people): """ Allows you to set/change who it's cc'ed to """ # check if it's been locked if isinstance(self._cc, tuple): raise ImmutableError("people", self) if type(people) == tuple: people = list(people) if type(people) != list: people = [people] for i, item in enumerate(people): if is_class(item): people[i] = item() if isinstance(people[i], self._pump.Person): people[i] = { "id": people[i].id, "objectType": people[i].objectType, } else: # must be collection/list people[i] = people[i].serialize(as_dict=True) self._cc = people def get_cc(self): return self._cc cc = property(fset=set_cc, fget=get_cc) def send(self): """ Sends the post to the server """ # lock the info in self._to = tuple(self._to) self._cc = tuple(self._cc) activity = { "verb":self.VERB, "object":{ "objectType":self.objectType, "content":self.content, }, "to": self._to, "cc": self._cc, } return self._post_activity(activity) def __repr__(self): return "<{type} by {name}>".format( type=self.TYPE, name=self.author.webfinger ) def __str__(self): return str(repr(self)) @classmethod def unserialize(cls, data, obj=None): """ Goes from JSON -> Note object """ id = data.get("id", None) content = data.get("content", u"") links = dict() for i in ["likes", "replies", "shares"]: if data.get(i, None): if "pump_io" in data[i]: links[i] = data[i]["pump_io"]["proxyURL"] else: links[i] = data[i]["url"] updated=parse(data["updated"]) published=parse(data["published"]) liked = data["liked"] if "liked" in data else False deleted = parse(data["deleted"]) if "deleted" in data else False author = cls._pump.Person.unserialize(data["author"]) if "author" in data else None if obj is None: return cls( id=id, content=content, to=(), # todo still. cc=(), # todo: ^^ updated=updated, published=published, links=links, liked=liked, deleted=deleted, author=author, ) else: obj.content = content obj.id = id obj.updated = updated obj.published = published obj._links = links obj.liked = liked obj.deleted = deleted obj.author = author if author else obj.author return obj python-pypump/pypump/models/person.py0000644000175000017500000002064112222046774016545 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from datetime import datetime from dateutil.parser import parse from requests_oauthlib import OAuth1 from pypump.openid import OpenID from pypump import exception from pypump.exception.PumpException import PumpException from pypump.models import AbstractModel from pypump.compatability import * @implement_to_string class Person(AbstractModel): _mapping = { "preferredUsername":"username", "displayName":"display_name", } TYPE = "person" ENDPOINT = "/api/user/{username}/feed" id = "" username = "" display_name = "" url = "" # url to profile updated = None # Last time this was updated published = None # when they joined (I think?) location = None # place item summary = "" # lil bit about them =] image = None # Image items inbox = None outbox = None is_self = False # is this you? def __init__(self, webfinger=None, id="", username="", url="", summary="", inbox=None, outbox=None, display_name="", image=None, published=None, updated=None, location=None, me=None, *args, **kwargs): """ id - the ID of the person. e.g. acct:Username@server.example username - persons username url - url to profile summary - summary of the user inbox - This is the person's inbox outbox - This is the person's outbox display_name - what the user want's to show up (defualt: username) image - image of the user (default: No image/None) published - when the user joined pump (default: now) updated - when the user last updated their profile (default: published) location - where the user resides (default: No location/None) me - you, used to set is_self, if not given it assumes this person _isn't_ you """ super(Person, self).__init__(*args, **kwargs) # okay we need to check if the webfinger is being used if isinstance(webfinger, string_types): # first clean up webfinger = webfinger.lstrip(" ").rstrip(" ") # okay now we need to look if it's on our servers or not. if "@" in webfinger: username, server = webfinger.split("@") else: # they probably just gave a username, the assumption is it's on our server! username, server = webfinger, self._pump.server if username == self._pump.nickname and server == self._pump.server: self.inbox = self._pump.Inbox(self) if inbox is None else inbox self.outbox = self._pump.Outbox(self) if outbox is None else outbox # now do some checking if server == self._pump.server: # cool we can get quite a bit of info. data = self._pump.request("/api/user/{0}/profile".format(username)) else: self.id = "acct:%s@%s" % (username, server) self.username = username self.server = server self.is_self = False url = "{proto}://{server}/api/user/{username}/profile".format( proto=self._pump.protocol, server=self.server, username=self.username ) # register client as we need to use the client credentials self.register_client() data = self._pump.request(url, client=self.client) self.unserialize(data, obj=self) return self.id = id self.inbox = self._pump.Inbox(self) if inbox is None else inbox self.username = username self.url = url self.summary = summary self.image = image if display_name: self.display_name = display_name else: self.display_name = self.username if published: self.published = published else: self.published = datetime.now() if updated: self.updated = updated else: self.updated = self.published if me and self.id == me.id: self.is_self = True self.outbox = self._pump.Outbox(self) self.message = self.outbox @property def webfinger(self): return self.id[5:] def register_client(self): """ Registers client on foreign server """ openid = OpenID( self.server, self._pump.client_name, self._pump.client_type ) openid.pypump = self._pump self.client_credentials = openid.register_client() self.client = OAuth1( client_key=self.client_credentials.key, client_secret=self.client_credentials.secret ) def follow(self): """ You follow this user """ activity = { "verb":"follow", "object":{ "id":self.id, "objectType":self.objectType, } } endpoint = self.ENDPOINT.format(username=self._pump.nickname) data = self._pump.request(endpoint, method="POST", data=activity) if "error" in data: raise PumpException(data["error"]) self.unserialize(data, obj=self) return True def unfollow(self): """ Unfollow a user """ activity = { "verb":"stop-following", "object":{ "id":self.id, "objectType":self.objectType, } } endpoint = self.ENDPOINT.format(username=self._pump.nickname) data = self._pump.request(endpoint, method="POST", data=activity) if "error" in data: raise PumpException(data["error"]) self.unserialize(data, obj=self) return True def __repr__(self): return "".format(person=self.id.replace("acct:", "")) def __str__(self): return self.__repr__() @classmethod def unserialize_service(cls, data, obj): """ Unserializes the data from a service """ id = data["id"] display = data["displayName"] updated = parse(data["updated"]) if "updated" in data else datetime.now() published = parse(data["published"]) if "published" in data else updated if obj is None: obj = cls() obj.id = id obj.display = display obj.updated = updated obj.published = published return obj @classmethod def unserialize(cls, data, obj=None): """ Goes from JSON -> Person object """ if data.get("objectType", "") == "service": return cls.unserialize_service(data, obj) self = cls() if obj is None else obj if "verb" in data and data["verb"] in ["follow", "stop-following"]: return None try: username = data["preferredUsername"] display = data["displayName"] except KeyError: # This will be fixed properly soon. return None self.id = data["id"] self.username = username self.display_name = display self.url = data["links"]["self"]["href"] self.summary = data["summary"] if "summary" in data else "" self.updated = parse(data["updated"]) if "updated" in data else datetime.now() self.published = parse(data["published"]) if "published" in data else self.updated self.me = True if "acct:%s@%s" % (cls._pump.nickname, cls._pump.server) == self.id else False self.location = cls._pump.Location.unserialize(data["location"]) if "location" in data else None self.updated = parse(data["updated"]) if "updated" in data else datetime.now() return self python-pypump/pypump/models/outbox.py0000644000175000017500000000206612222046774016560 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from pypump.compatability import * from pypump.models.feed import Feed class Outbox(Feed): OBJECT_TYPES = "Activity" @property def ENDPOINT(self): if self._ENDPOINT is not None: return self._ENDPOINT self._ENDPOINT = "api/user/{username}/feed".format(username=self.actor.username) return self._ENDPOINT python-pypump/pypump/models/list.py0000644000175000017500000000663312222046774016217 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## import json from pypump.models import AbstractModel from pypump.exception import PyPumpException from pypump.compatability import * @implement_to_string class List(AbstractModel): TYPE = "Collection" ENDPOINT = "api/user/{username}/lists/{name}" id = None name = None def __init__(self, name, id=None, *args, **kwargs): self.name = name self.id = self.id if id is None else id if self.id is None and self.name: user_lists = self.all() for user_list in user_lists: if user_list.name == self.name: self.id = user_list.id if self.id is None: error = "Can't find list with name {0!r} (Lists found: {1})".format( self.name, ", ".join(user_lists)) PyPumpException(error) super(List, self).__init__(*args, **kwargs) @classmethod def all(cls): """ Lists all of the users lists """ data = cls._pump.request(cls.ENDPOINT.format( username=cls._pump.nickname, name="person" )) lists = list() for item in data["items"]: lists.append(cls.unserialize(item)) return lists def serialize(self, as_dict=False): """ Serializes data out to server """ data = { "id": self.id, "objectType": self.objectType, } if as_dict: return data return json.dumps(data) @classmethod def unserialize(cls, data, obj=None): """ Takes data from the server and builds list """ id = data["id"] name = data["displayName"] if obj is None: obj = cls(name=name, id=id) else: obj.id = id obj.name = name return obj class Public(List): ENDPOINT = "http://activityschema.org/collection/public" def __init__(self, *args, **kwargs): self.id = self.ENDPOINT super(Public, self).__init__(name=self.TYPE, *args, **kwargs) class Followers(List): ENDPOINT = "api/user/{username}/followers" def __init__(self, *args, **kwargs): self.ENDPOINT = self._pump.build_url(self.ENDPOINT.format(username=self._pump.nickname)) self.id = self.ENDPOINT super(Followers, self).__init__(name=self.TYPE, *args, **kwargs) class Following(List): ENDPOINT = "api/user/{username}/following" def __init__(self, *args, **kwargs): self.ENDPOINT = self._pump.build_url(self.ENDPOINT.format(username=self._pump.nickname)) self.id = self.ENDPOINT super(Following, self).__init__(name=self.TYPE, *args, **kwargs) python-pypump/pypump/models/__init__.py0000644000175000017500000001422612222046774017000 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## import json class AbstractModel(object): @property def TYPE(self): return self.__class__.__name__ @property def objectType(self): return self.TYPE.lower() _mapping = { "objectType":"TYPE", } _pump = None def __init__(self, pypump=None, *args, **kwargs): """ Sets up pump instance """ if pypump: self._pump = pypump def _post_activity(self, activity): """ Posts a activity to feed """ data = self._pump.request(self.ENDPOINT, method="POST", data=activity) if not data: return False if "error" in data: raise PumpException(data["error"]) self.unserialize(data["object"], obj=self) return True def serialize(self, *args, **kwargs): """ Changes it from obj -> JSON """ data = {} for item in dir(self): if item.startswith("_"): continue # we don't want value = getattr(self, item) # we need to double check we're not in mapper item = self.remap(item) data[item] = value return json.dumps(data, *args, **kwargs) @staticmethod def unserialize(self, data, *args, **kwargs): """ Changes it from JSON -> obj """ data = json.loads(data) klass = self(pypump=self._pump) for key, value in data.items(): key = self.remap(key) if key is None: continue setattr(klass, key, value) return klass def remap(self, data): """ Remaps """ if data in self._mapping.keys(): return self._mappping[data] elif data in self.__mapping.values(): for k, v in self._mapping.items(): if data == v: return k return data class Likeable(object): """ Provides the model with the like and unlike methods as well as the property likes which will look up who's liked the model instance and return you back a list of user objects must have _links["likes"] """ @property def likes(self): """ Gets who's liked this object """ endpoint = self._links["likes"] likes = self._pump.request(endpoint, raw=True) likes_obj = [] for l in likes.get("items", likes_obj): likes_obj.append(self._pump.Person.unserialize(l)) return likes_obj favorites = likes def like(self, verb="like"): """ Likes the model """ activity = { "verb": verb, "object": { "id": self.id, "objectType": self.objectType, } } self._post_activity(activity) def unlike(self, verb="unlike"): """ Unlikes the model """ activity = { "verb": verb, "object": { "id": self.id, "objectType": self.objectType, } } self._post_activity(activity) def favorite(self): """ Favourite model """ return self.like(verb="favorite") def unfavorite(self): """ Unfavourite model """ return self.unlike(verb="unfavorite") class Commentable(object): """ Provides the model with the comment method allowing you to post a comment to on the model. It also provides an ability to read comments. must have _likes["replies"] """ @property def comments(self): """ Fetches the comment objects for the models """ endpoint = self._links["replies"] comments = self._pump.request(endpoint, raw=True) comments_obj = [] for c in comments.get("items", comments_obj): comments_obj.append(self._pump.Comment.unserialize(c)) return comments_obj def comment(self, comment): """ Posts a comment object on model """ comment.inReplyTo = self comment.send() class Shareable(object): """ Provides the model with the share and unshare methods and shares property allowing you to see who's shared the model. must have _likes["shares"] """ @property def shares(self): """ Fetches the people who've shared the model """ endpoint = self._links["shares"] shares = self._pump.request(endpoint, raw=True) shares_obj = [] for p in shares.get("items", shares_obj): shares_obj.append(self._pump.Person.unserialize(p)) return shares_obj def share(self): """ Shares the model """ activity = { "verb": "share", "object": { "id": self.id, "objectType": self.objectType, }, } self._post_activity(activity) def unshare(self): """ Unshares a previously shared model """ activity = { "verb": "unshare", "object": { "id": self.id, "objectType": self.objectType, }, } self._post_activity(activity) class Deleteable(object): """ Provides the model with the ability to be deleted """ def delete(self): """ Delete's a model """ activity = { "verb": "delete", "object": { "id": self.id, "objectType": self.objectType, } } self._post_activity(activity) python-pypump/pypump/models/comment.py0000644000175000017500000001047412222046774016704 0ustar tagtag## # Copyright (C) 2013 Jessica T. (Tsyesika) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## from datetime import datetime from dateutil.parser import parse from pypump.models import (AbstractModel, Commentable, Likeable, Shareable, Deleteable) from pypump.compatability import * @implement_to_string class Comment(AbstractModel, Likeable, Shareable, Deleteable, Commentable): VERB = "post" @property def ENDPOINT(self): return "/api/user/{username}/feed".format( username=self._pump.nickname ) id = None content = "" inReplyTo = None updated = None published = None deleted = False author = None _links = None def __init__(self, content, id=None, inReplyTo=None, published=None, updated=None, deleted=False, liked=False, author=None, links=None, *args, **kwargs): super(Comment, self).__init__(*args, **kwargs) self.id = "" if id is None else id self.content = content self.inReplyTo = inReplyTo self.published = published self.updated = updated self.deleted = deleted self.liked = liked self.author = self._pump.Person(self._pump.nickname) if author is None else author self._links = dict() if links is None else links def __repr__(self): return "<{type} by {webfinger}>".format( type=self.TYPE, webfinger=self.author.webfinger ) def __str__(self): return str(repr(self)) def _post_activity(self, activity): """ POSTs activity and updates self with new data in response """ data = self._pump.request(self.ENDPOINT, method="POST", data=activity) if not data: return False if "error" in data: raise PumpException(data["error"]) self.unserialize(data["object"], obj=self) return True def send(self): activity = { "verb":self.VERB, "object":{ "objectType":self.objectType, "content":self.content, "inReplyTo":{ "id":self.inReplyTo.id, "objectType":self.inReplyTo.objectType, }, }, } return self._post_activity(activity) @classmethod def unserialize(cls, data, obj=None): """ from JSON -> Comment """ content = data["content"] if "content" in data else "" id = data["id"] if "id" in data else "" published = parse(data["published"]) updated = parse(data["updated"]) if "updated" in data else False deleted = parse(data["deleted"]) if "deleted" in data else False liked = data["liked"] if "liked" in data else False author = cls._pump.Person.unserialize(data["author"]) if "author" in data else None links = dict() for i in ["likes", "replies", "shares"]: if data.get(i, None): if "pump_io" in data[i]: links[i] = data[i]["pump_io"]["proxyURL"] else: links[i] = data[i]["url"] if obj is None: return cls( content=content, id=id, published=published, updated=updated, deleted=deleted, liked=liked, author=author, links=links ) obj.content = content obj.id = id obj.published = published obj.updated = updated obj.deleted = deleted obj.liked = liked obj.author = author if author else obj.author obj._links = links return obj python-pypump/pypump/loader.py0000644000175000017500000000240212222046743015211 0ustar tagtagfrom pypump.compatability import * from pypump.models import AbstractModel import imp import glob class Loader(object): _models = {} def __init__(self, pypump): # goes through and and populates the models with pypump self._pypump = pypump for model in glob.glob("models/*.py"): self.load_model(model) def preload(self, path): """ Called before the load of a plugin """ pass # nothing to do def postload(self, name, model): """ Called after loading a plugin with the models """ for klass in dir(model): klass_obj = getattr(model, klass) if is_class(klass_obj) and issubclass(klass_obj, AbstractModel): klass_obj._pump = self._pypump setattr(self._pypump, klass, klass_obj) def load_model(self, path): """ Loads a model from a path """ name = self.get_name(path) self.preload(path) self._models[name] = imp.load_source(name, path) self.postload(name, self._models[name]) def get_name(self, path): """ Gets the name from the path """ # todo: make it work on non-unix systems name = path.split("/")[-1] name = name.replace(".py", "") return name python-pypump/MANIFEST.in0000644000175000017500000000021212222046743013572 0ustar tagtaginclude *.py include README.rst include COPYING include AUTHORS.md include VERSION recursive-include docs * recursive-include pypump *.py python-pypump/COPYING0000644000175000017500000010451312222046743013100 0ustar tagtag GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .