New Upstream Release - python-sunlight

Ready changes

Summary

Merged new upstream version: 1.2.9 (was: 1.1.5).

Resulting package

Built on 2023-01-11T14:19 (took 7m11s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python-sunlight-docapt install -t fresh-releases python3-sunlight

Lintian Result

Diff

diff --git a/AUTHORS b/AUTHORS
index 4bfe158..87a739e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,2 +1,4 @@
 Paul Tagliamonte <paultag@sunlightfoundation.com>
 James Turk <jturk@sunlightfoundation.com>
+Jeremy Carbaugh <jcarbaugh@sunlightfoundation.com>
+Daniel Cloud <dcloud@sunlightfoundation.com>
diff --git a/ChangeLog b/ChangeLog
index 0275223..0aa5a5c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,132 @@
+2015-01-09  Paul Tagliamonte  <paultag@sunlightfoundation.com>
+
+* Bump version to 1.2.8
+
+2014-12-05  Daniel Cloud  <dcloud@sunlightfoundation.com>
+* Add option for choosing http/https in Congress service. Still https by default.
+* Improve detection of exposable methods for services in command line.
+* Write tests that can use httpretty (but keep commented out bc bug in httpretty)
+* Fix subclassing of Service class
+* Expose an error parsing response as a SunlightException (in Congress service)
+
+2014-12-04  Daniel Cloud  <dcloud@sunlightfoundation.com>
+* Fix cli parsing of arguments, use newer version of clint.
+* Make EntityList and EntityDict subclass list and dict directly.
+* Fix logging configuration in cache.py
+* Add 'documents' and 'congressional_documents' methods to congress module for new endpoints.
+* Update documentation for congress module
+
+2014-11-25  Paul Tagliamonte  <paultag@sunlightfoundation.com>
+
+* Merge pull request #20, fixing pagination for the congress API.
+* Bump version to 1.2.7
+
+2014-04-04  Daniel Cloud  <dcloud@sunlightfoundation.com>
+
+* Fix for congress.legislator methods, including adding support for OCD id.
+  Make sure to return None when there are no results.
+* Add tests for congress.legislator method, including lookup by thomas and ocd
+  identifiers.
+* Further embrache path as list in congress, and test it.
+* Fix congress _get_url to conform to new Service.get requirements
+  (42782a8eed). May close #13.
+* Update capitolwords endpoint with version number; Fix _get_url to conform to
+  new Service.get requirements. See also #13.
+
+2014-03-12  Thom Neale  <tneale@sunlightfoundation.com>
+
+* sunlight/service.py
+ - Added url encoding for url path segments.
+
+2014-01-13  Eric Mill  <eric@sunlightfoundation.com>
+
+* Change to HTTPS endpoint for congress API
+
+2014-01-24  Daniel Cloud <dcloud@sunlightfoundation.com>
+
+* sunlight/debugcache.py -> sunlight/cache.py
+ - Renamed debugcache module to cache. Renamed cache instance variable to
+   response_cache
+* sunlight/__init__.py:
+ - Rename cache alias to response_cache.
+* Added documentation for cache and pagination
+
+2014-01-24  Jeremy Carbaugh <jcarbaugh@sunlightfoundation.com>
+
+* sunlight/pagination.py
+- Made a PagingService that API services can opt in to.
+* sunlight/services/congress.py
+- Made congress support PagingService.
+
+2013-11-22  Paul Tagliamonte  <paultag@sunlightfoundation.com>
+
+* sunlight/services/opencivic.py
+ - New service for OpenCivic endpoints. Currently hardcoded to talk with
+   opencivicdata.org.
+
+2013-11-20  Thom Neale  <tneale@sunlightfoundation.com>
+
+* sunlight/debugcache.py:
+ - Added a method decorator for caching API responses, base classes for
+   adding more backends if need arises
+* sunlight/__init__.py:
+ - Imports the cache instance and aliases it
+* sunlight/services.py:
+ - Imports the cache instance and decorates Service.get with it
+ - If the cache is never enabled, everything words the way it always has
+ - If the cache is enabled with cache.enable(), API responses get cached
+
+2013-11-08  Daniel Cloud  <dcloud@sunlightfoundation.com>
+
+* sunlight/cli.py:
+ - Fix json serialization of EntityList and EntityDict
+
+2013-09-18  Daniel Cloud  <dcloud@sunlightfoundation.com>
+
+* sunlight/service.py:
+ - Add EntityList and EntityDict subclasses that provide metadata
+* sunlight/services/congress.py:
+ - Add new nominations endpoint
+
+2013-07-24  Daniel Cloud  <dcloud@sunlightfoundation.com>
+
+* sunlight/services/congress.py:
+ - Work to support the new Congress API
+
+2013-04-26  Paul Tagliamonte  <paultag@sunlightfoundation.com>
+
+* sunlight/cli.py
+ - Convert a dict comprehension into a dict() call on a list comprehension
+   that produces tuples.
+* Bump release to 1.1.8
+
+2012-12-13  Jeremy Carbaugh  <jcarbaugh@sunlightfoundation.com>
+
+* setup.py:
+ - add install_requires and entry_points
+
+2012-12-12  Paul Tagliamonte  <paultag@sunlightfoundation.com>
+
+* sunlight/services/*py:
+ - Rename classes to not be gross.
+* Bump release to 1.1.7
+
+2012-12-12  Jeremy Carbaugh  <jcarbaugh@sunlightfoundation.com>
+
+* sunlight/cli.py:
+ - Add a clint CLI wrapper for python-sunlight
+* sunlight/__init__.py:
+ - Add a method to get services provided.
+
+2012-11-15  Paul Tagliamonte  <paultag@sunlightfoundation.com>
+
+* sunlight/services/congress.py:
+ - Add **kwargs for each method, and pass them along to all requests. This
+   helps prevent cases where we add an argument and older versions break.
+* sunlight/services/*.py:
+ - Fixed some outstanding pep8 issues.
+* Bump release to 1.1.6
+
 2012-06-29  James Turk  <jturk@sunlightfoundation.com>
 
 * sunlight/service.py:
diff --git a/PKG-INFO b/PKG-INFO
index 4c27e3d..b65f74e 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: sunlight
-Version: 1.1.5
+Version: 1.2.9
 Summary: Unified Sunlight API bindings
 Home-page: https://github.com/sunlightlabs/python-sunlight
 Author: Paul Tagliamonte
@@ -30,7 +30,7 @@ Description: python-sunlight
             echo "API_KEY" > ~/.sunlight.key
         
         where `API_KEY` is actually the string of letters and numbers that was emailed
-        to you. Actually putting `API_KEY` into this file will most likely result in 
+        to you. Actually putting `API_KEY` into this file will most likely result in
         an error from the server. You can confirm they key with the following command:
         
             cat ~/.sunlight.key
@@ -42,10 +42,10 @@ Description: python-sunlight
         
         Basic usage and some brief examples can be found on
         `readthedocs <http://python-sunlight.rtfd.org>`_.
-        If this doens't help, feel free to email for help, ask over IRC in
+        If this doesn't help, feel free to email for help, ask over IRC in
         ``#sunlightlabs`` on ``irc.freenode.net``, or open a issue if it's a
         particularly nasty bug (particularly regarding ambiguous documentation, or
-        poorly exposed API methods). 
+        poorly exposed API methods).
         
         License
         *******
@@ -63,3 +63,9 @@ Description: python-sunlight
         setup.py script.
         
 Platform: any
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
diff --git a/README.rst b/README.rst
index 0903a14..17fb76f 100644
--- a/README.rst
+++ b/README.rst
@@ -22,7 +22,7 @@ The easiest way to write this file out is by running a command similar to this
     echo "API_KEY" > ~/.sunlight.key
 
 where `API_KEY` is actually the string of letters and numbers that was emailed
-to you. Actually putting `API_KEY` into this file will most likely result in 
+to you. Actually putting `API_KEY` into this file will most likely result in
 an error from the server. You can confirm they key with the following command:
 
     cat ~/.sunlight.key
@@ -34,10 +34,10 @@ Help me!
 
 Basic usage and some brief examples can be found on
 `readthedocs <http://python-sunlight.rtfd.org>`_.
-If this doens't help, feel free to email for help, ask over IRC in
+If this doesn't help, feel free to email for help, ask over IRC in
 ``#sunlightlabs`` on ``irc.freenode.net``, or open a issue if it's a
 particularly nasty bug (particularly regarding ambiguous documentation, or
-poorly exposed API methods). 
+poorly exposed API methods).
 
 License
 *******
diff --git a/debian/changelog b/debian/changelog
index 34da42a..4941e51 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,9 @@
-python-sunlight (1.1.5-5) UNRELEASED; urgency=medium
+python-sunlight (1.2.9-1) UNRELEASED; urgency=medium
 
   * Update standards version to 4.6.2, no changes needed.
+  * New upstream release.
 
- -- Debian Janitor <janitor@jelmer.uk>  Wed, 11 Jan 2023 12:28:40 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 11 Jan 2023 14:12:26 -0000
 
 python-sunlight (1.1.5-4) unstable; urgency=medium
 
diff --git a/debian/patches/0001-Fix-FTBFS-with-sphinx-1.8.patch b/debian/patches/0001-Fix-FTBFS-with-sphinx-1.8.patch
index 07e1a8b..b6f8479 100644
--- a/debian/patches/0001-Fix-FTBFS-with-sphinx-1.8.patch
+++ b/debian/patches/0001-Fix-FTBFS-with-sphinx-1.8.patch
@@ -3,10 +3,10 @@ Subject: Fix-FTBFS-with-sphinx-1.8
 sphinx.ext.pngmath has been removed in favor of sphinx.ext.imgmath.
 replace the former with the latter in docs/source/conf.py.
 Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=922261
-diff --git a/docs/source/conf.py b/docs/source/conf.py
-index 8ec8ee0..c0c7dd5 100644
---- a/docs/source/conf.py
-+++ b/docs/source/conf.py
+Index: python-sunlight.git/docs/source/conf.py
+===================================================================
+--- python-sunlight.git.orig/docs/source/conf.py
++++ python-sunlight.git/docs/source/conf.py
 @@ -17,7 +17,7 @@ version = sunlight.__version__
  # Add any Sphinx extension module names here, as strings. They can be extensions
  # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
diff --git a/docs/Makefile b/docs/Makefile
index 29fd034..5676e76 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -41,6 +41,9 @@ html:
 	@echo
 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 
+livehtml:
+	sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+
 dirhtml:
 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 	@echo
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 8ec8ee0..1101cba 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -34,7 +34,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'python-sunlight'
-copyright = u'2012, Sunlight Labs'
+copyright = u'2014, Sunlight Labs'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -84,7 +84,16 @@ pygments_style = 'sphinx'
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+
+# on_rtd is whether we are on readthedocs.org
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+
+if not on_rtd:  # only import and set the theme if we're building docs locally
+    import sphinx_rtd_theme
+    html_theme = 'sphinx_rtd_theme'
+    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# otherwise, readthedocs.org uses their theme by default, so no need to specify it
 
 # 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
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 7073f38..b0ec680 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -11,13 +11,12 @@ projects.
 
 Currently this library supports the following APIs:
 
-* `Sunlight Congress API <http://services.sunlightlabs.com/docs/Sunlight_Congress_API/>`_ (via :ref:`sunlight.congress`)
 * `Open States API <http://openstates.org/api/>`_ (via :ref:`sunlight.openstates`)
+* `Congress API <http://sunlightlabs.github.com/congress/>`_ (via :ref:`sunlight.congress`)
 * `Capitol Words API <http://capitolwords.org/api/>`_ (via :ref:`sunlight.capitolwords`)
 
-Support is pending for the `Influence Explorer API <http://data.influenceexplorer.com/api/>`_ and the
-`Real Time Congress API <http://services.sunlightlabs.com/docs/Real_Time_Congress_API/>`_,
-though these currently have their own Python wrappers.
+* `The old Sunlight Congress API (deprecated) <http://services.sunlightlabs.com/docs/Sunlight_Congress_API/>`_ (via :ref:`sunlight.congress_deprecated`)
+
 
 Installation
 ============
@@ -46,7 +45,7 @@ After setting your API key simply ``import sunlight`` and start using the APIs::
 You can also import a specific API client::
 
     >>> from sunlight import congress
-    >>> pelosi = congress.legislators(lastname='Pelosi')[0]
+    >>> pelosi = congress.legislators(last_name='Pelosi')[0]
 
 
 For details on how to use the various APIs check out the documentation for the
@@ -59,6 +58,17 @@ individual clients:
    services/openstates.rst
    services/capitolwords.rst
 
+Extras
+======
+
+Useful utilities for working with the services.
+
+.. toctree::
+   :maxdepth: 2
+
+   sunlight/cache.rst
+   sunlight/pagination.rst
+
 Internals
 =========
 
diff --git a/docs/source/services/capitolwords.rst b/docs/source/services/capitolwords.rst
index 9311d68..67cf3f4 100644
--- a/docs/source/services/capitolwords.rst
+++ b/docs/source/services/capitolwords.rst
@@ -10,8 +10,8 @@ giving you an at-a-glance view of which issues lawmakers address on a
 daily, weekly, monthly and yearly basis. Capitol Words lets you see
 what are the most popular words spoken by lawmakers on the House and Senate floor.
 
+CapitolWords
+============
 
-.. automethod:: sunlight.services.capitolwords.capitolwords.dates
-.. automethod:: sunlight.services.capitolwords.capitolwords.phrases
-.. automethod:: sunlight.services.capitolwords.capitolwords.phrases_by_entity
-.. automethod:: sunlight.services.capitolwords.capitolwords.text
+.. autoclass:: sunlight.services.capitolwords.CapitolWords
+    :members:
diff --git a/docs/source/services/congress.rst b/docs/source/services/congress.rst
index 1b37c7f..94a0e65 100644
--- a/docs/source/services/congress.rst
+++ b/docs/source/services/congress.rst
@@ -4,42 +4,123 @@
 sunlight.congress
 =================
 
-The `Sunlight Labs Congress API
-<http://services.sunlightlabs.com/docs/Sunlight_Congress_API/>`_
-provides methods for obtaining basic information on Members of Congress,
-legislator IDs used by various websites, and geographical lookups between
-places and the politicians that represent them. The primary purpose of
-the API is to facilitate mashups involving politicians and the various
-other APIs that are out there.
+The `Sunlight Congress API
+<http://sunlightlabs.github.io/congress/>`_
+provides methods for obtaining information for the people and work of
+Congress. Information on legislators, districts, committees, bills,
+votes, as well as real-time notice of hearings, floor activity and
+upcoming bills.
 
+.. autoclass:: sunlight.services.congress.Congress
+
+    Class methods that do not specify positional arguments accept `filtering <http://sunlightlabs.github.io/congress/index.html#filtering>`_, `pagination <http://sunlightlabs.github.io/congress/index.html#pagination>`_, and `sorting <http://sunlightlabs.github.io/congress/index.html#sorting>`_ parameters as keyword arguments. See each method's API page for specfic filtering arguments.
 
 Legislators
 ===========
 
-This set of Congress API methods deal with federal legislators.
+This set of method provides various ways to search or look up federal legislators.
+For detailed documentaion on the return value of these methods see
+`legislator fields <http://sunlightlabs.github.io/congress/legislators.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.legislators
+.. automethod:: sunlight.services.congress.Congress.legislator
+.. automethod:: sunlight.services.congress.Congress.all_legislators_in_office
+.. automethod:: sunlight.services.congress.Congress.locate_legislators_by_lat_lon
+.. automethod:: sunlight.services.congress.Congress.locate_legislators_by_zip
+
+Bills
+=====
+
+This set of method provides various ways to search or look up Congressional bills.
 For detailed documentaion on the return value of these methods see
-`legislator fields <http://services.sunlightlabs.com/docs/congressapi/legislators.get(List)/>`_.
+`bill fields <http://sunlightlabs.github.io/congress/bills.html#fields>`_.
 
-.. automethod:: sunlight.services.congress.congress.legislators
-.. automethod:: sunlight.services.congress.congress.legislator_search
-.. automethod:: sunlight.services.congress.congress.legislators_for_zip
-.. automethod:: sunlight.services.congress.congress.legislators_for_lat_lon
+.. automethod:: sunlight.services.congress.Congress.bills
+.. automethod:: sunlight.services.congress.Congress.bill
+.. automethod:: sunlight.services.congress.Congress.search_bills
+.. automethod:: sunlight.services.congress.Congress.upcoming_bills
 
 Districts
 =========
 
-Pair of methods for retrieving districts.  District dictionaries have a `'state'` and `'number'` key
-(ex. `{'state': 'NC', 'number': '3'}`)
+Pair of methods for retrieving districts.  District dictionaries have a `'state'` and `'district'` key
+(ex. `{'state': 'NC', 'district': '4'}`).
 
 
-.. automethod:: sunlight.services.congress.congress.districts_for_zip
-.. automethod:: sunlight.services.congress.congress.districts_for_lat_lon
+.. automethod:: sunlight.services.congress.Congress.locate_districts_by_lat_lon
+.. automethod:: sunlight.services.congress.Congress.locate_districts_by_zip
 
 Committees
 ==========
 
-Methods for dealing with committees.
+The following methods search legislative committees.
+For detailed documentaion on the return value of these methods see
+`committee fields <http://sunlightlabs.github.io/congress/committees.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.committees
+
+Amendments
+==========
+
+The following methods search amendments.
+For detailed documentaion on the return value of these methods see
+`committee fields <http://sunlightlabs.github.io/congress/amendments.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.amendments
+
+Votes
+=====
+
+The following methods search votes.
+For detailed documentaion on the return value of these methods see
+`vote fields <http://sunlightlabs.github.io/congress/votes.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.votes
+
+Floor Updates
+=============
+
+The following methods search floor updates.
+For detailed documentaion on the return value of these methods see
+`floor update fields <http://sunlightlabs.github.io/congress/floor_updates.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.floor_updates
+
+Hearings
+========
+
+The following methods search committee hearings.
+For detailed documentaion on the return value of these methods see
+`hearing fields <http://sunlightlabs.github.io/congress/hearings.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.hearings
+
+Nominations
+===========
+
+The following methods search nominations made by the President of the United States.
+For detailed documentaion on the return value of these methods see
+`nominations fields <http://sunlightlabs.github.io/congress/nominations.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.nominations
+
+Congressional Documents
+=======================
+
+The following methods search congressional documents including House witness documents and
+House committee reports.
+For detailed documentaion on the return value of these methods see
+`congressional documents fields <http://sunlightlabs.github.io/congress/congressional_documents.html#fields>`_.
+
+.. automethod:: sunlight.services.congress.Congress.congressional_documents
+
+Documents
+=========
+
+The following methods search a wide range of documents including Government Accountability
+Office (GAO) Reports and Inspector General Reports. These government oversight documents investigate
+misconduct, waste and programs.
+For detailed documentaion on the return value of these methods see
+`documents fields <http://sunlightlabs.github.io/congress/documents.html#fields>`_.
 
-.. automethod:: sunlight.services.congress.congress.committees
-.. automethod:: sunlight.services.congress.congress.committee_detail
-.. automethod:: sunlight.services.congress.congress.committees_for_legislator
+.. automethod:: sunlight.services.congress.Congress.documents
\ No newline at end of file
diff --git a/docs/source/services/congress_deprecated.rst b/docs/source/services/congress_deprecated.rst
new file mode 100644
index 0000000..add56d1
--- /dev/null
+++ b/docs/source/services/congress_deprecated.rst
@@ -0,0 +1,10 @@
+.. _sunlight.congress_deprecated:
+
+=================
+sunlight.congress_deprecated
+=================
+
+The `Sunlight Labs Congress API
+<http://services.sunlightlabs.com/docs/Sunlight_Congress_API/>`_
+is deprecated in favor of the new `Sunlight Congress API <https://sunlightlabs.github.io/congress/migration.html>`_
+
diff --git a/docs/source/services/openstates.rst b/docs/source/services/openstates.rst
index d39d0e7..a0a2c7a 100644
--- a/docs/source/services/openstates.rst
+++ b/docs/source/services/openstates.rst
@@ -23,49 +23,49 @@ Metadata
 
 Methods for dealing with `Open States Metadata <http://openstates.org/api/metadata/#metadata-fields>`_.
 
-.. automethod:: sunlight.services.openstates.openstates.all_metadata
-.. automethod:: sunlight.services.openstates.openstates.state_metadata
+.. automethod:: sunlight.services.openstates.Openstates.all_metadata
+.. automethod:: sunlight.services.openstates.Openstates.state_metadata
 
 Bills
 =====
 
 Methods for dealing with `Open States Bills <http://openstates.org/api/bills/#bill-fields>`_.
 
-.. automethod:: sunlight.services.openstates.openstates.bills
-.. automethod:: sunlight.services.openstates.openstates.bill_detail
+.. automethod:: sunlight.services.openstates.Openstates.bills
+.. automethod:: sunlight.services.openstates.Openstates.bill_detail
 
 Legislators
 ===========
 
 Methods for dealing with `Open States Legislators <http://openstates.org/api/legislators/#legislator-fields>`_.
 
-.. automethod:: sunlight.services.openstates.openstates.legislators
-.. automethod:: sunlight.services.openstates.openstates.legislator_detail
-.. automethod:: sunlight.services.openstates.openstates.legislator_geo_search
+.. automethod:: sunlight.services.openstates.Openstates.legislators
+.. automethod:: sunlight.services.openstates.Openstates.legislator_detail
+.. automethod:: sunlight.services.openstates.Openstates.legislator_geo_search
 
 Committees
 ==========
 
 Methods for dealing with `Open States Committees <http://openstates.org/api/committees/#committee-fields>`_.
 
-.. automethod:: sunlight.services.openstates.openstates.committees
-.. automethod:: sunlight.services.openstates.openstates.committee_detail
+.. automethod:: sunlight.services.openstates.Openstates.committees
+.. automethod:: sunlight.services.openstates.Openstates.committee_detail
 
 Districts
 =========
 
 Methods for dealing with `Open States Districts <http://openstates.org/api/districts/#district-fields>`_.
 
-.. automethod:: sunlight.services.openstates.openstates.districts
-.. automethod:: sunlight.services.openstates.openstates.district_boundary
+.. automethod:: sunlight.services.openstates.Openstates.districts
+.. automethod:: sunlight.services.openstates.Openstates.district_boundary
 
 Events
 ======
 
 Methods for dealing with `Open States Events <http://openstates.org/api/events/#event-fields>`_.
 
-.. automethod:: sunlight.services.openstates.openstates.events
-.. automethod:: sunlight.services.openstates.openstates.event_detail
+.. automethod:: sunlight.services.openstates.Openstates.events
+.. automethod:: sunlight.services.openstates.Openstates.event_detail
 
 
 Examples
@@ -88,7 +88,7 @@ Bills::
 Legislators::
 
     from sunlight import openstates
-    
+
     ca_dems = openstates.legislators(
         state='ca',
         party='Democratic',
@@ -111,10 +111,10 @@ Committees::
 Events::
 
     from sunlight import openstates
-    
+
     tx_events = openstates.events( state='tx', type='committee:meeting' )
     for event in tx_events:
         print "Event @ %s" % event['when']
         for who in event['participants']:
             print "  %s (%s)" % ( who['participant'], who['chamber'] )
-                        
+
diff --git a/docs/source/sunlight/cache.rst b/docs/source/sunlight/cache.rst
new file mode 100644
index 0000000..4b8195d
--- /dev/null
+++ b/docs/source/sunlight/cache.rst
@@ -0,0 +1,11 @@
+sunlight.cache
+===============
+
+This module provides caching with support for different backends. The default implementation is `ResponseCache`.
+
+Methods and Constants
+*********************
+
+.. automodule:: sunlight.cache
+   :members:
+
diff --git a/docs/source/sunlight/pagination.rst b/docs/source/sunlight/pagination.rst
new file mode 100644
index 0000000..ec0ac31
--- /dev/null
+++ b/docs/source/sunlight/pagination.rst
@@ -0,0 +1,11 @@
+sunlight.pagination
+===============
+
+This module provides a pagination wrapper for services that support it. Currently only :doc:`/services/congress` supports this.
+
+Methods and Constants
+*********************
+
+.. automodule:: sunlight.pagination
+   :members:
+
diff --git a/examples/congress/who_is_my_rep b/examples/congress/who_is_my_rep
new file mode 100755
index 0000000..9157ed6
--- /dev/null
+++ b/examples/congress/who_is_my_rep
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+from sunlight import congress
+import json
+import os
+
+obj = json.load(open(os.path.expanduser("~/.location.json"), "r"))
+lat, lon = obj['latitude'], obj['longitude']
+
+
+print(congress.locate_legislators_by_zip("02481"))
diff --git a/examples/congress/get_nacy b/examples/congress_deprecated/get_nacy
similarity index 80%
rename from examples/congress/get_nacy
rename to examples/congress_deprecated/get_nacy
index 75e1219..07b9ccb 100755
--- a/examples/congress/get_nacy
+++ b/examples/congress_deprecated/get_nacy
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 from __future__ import print_function
-from sunlight import congress
+from sunlight import congress_deprecated as congress
 
 nancy = congress.legislators( lastname="Pelosi" )[0]
 print( "%s %s %s (%s)" % ( nancy['title'], nancy['firstname'],
diff --git a/examples/cross-api/death-metal b/examples/cross-api/death-metal
index a109666..47d387e 100755
--- a/examples/cross-api/death-metal
+++ b/examples/cross-api/death-metal
@@ -12,9 +12,9 @@ for cw_record in capitolwords.phrases_by_entity(
     "legislator",  # We're getting all legislators
     sort="count",  # sorted by how much they say
     phrase=phrase, # this word
-)[:6]: # We'll just try the top 5 legislators
+)[:6]: # We'll just try the top 6 legislators
     legislator = congress.legislators(
-        bioguide_id=cw_record['legislator'], # Look up this biogude (unique ID)
+        bioguide_id=cw_record['legislator'], # Look up this bioguide (unique ID)
         #                                      for every fed. legislator
         all_legislators="true" # search retired legislators
     )
diff --git a/examples/influenceexplorer/contrib_to_mikulski b/examples/influenceexplorer/contrib_to_mikulski
deleted file mode 100755
index 6ea3d6e..0000000
--- a/examples/influenceexplorer/contrib_to_mikulski
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-from sunlight import influenceexplorer
-
-contrib = influenceexplorer.contributions(
-    contributor_state="md|va",
-    recipient_ft="mikulski",
-    cycle="2008" )
-
-for contributor in contrib:
-    print( "%s from %s contributed %s" % (
-        contributor['contributor_name'],
-        contributor['contributor_city'],
-        contributor['amount']
-    ))
diff --git a/examples/influenceexplorer/homeland_security_grants b/examples/influenceexplorer/homeland_security_grants
deleted file mode 100755
index 8a703e0..0000000
--- a/examples/influenceexplorer/homeland_security_grants
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-from sunlight import influenceexplorer
-
-grants = influenceexplorer.grants(
-    agency_ft="Homeland Security"
-)
-
-for grant in grants:
-    print( "%s to %s for %s" % (
-        grant['total_funding_amount'],
-        grant['recipient_name'],
-        grant['project_description']
-    ))
diff --git a/examples/influenceexplorer/lobby_for_pfizer b/examples/influenceexplorer/lobby_for_pfizer
deleted file mode 100755
index f2c4ddc..0000000
--- a/examples/influenceexplorer/lobby_for_pfizer
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-from sunlight import influenceexplorer
-
-lobs = influenceexplorer.lobbying(
-    client_ft="pfizer",
-    year="2011"
-)
-
-for lob in lobs:
-    print( lob['registrant_name'] )
-    for issue in lob['issues']:
-        print( "  %s (%s)" % ( issue['general_issue'], issue['specific_issue'] ))
diff --git a/examples/influenceexplorer/microsoft_contracts b/examples/influenceexplorer/microsoft_contracts
deleted file mode 100755
index 59c74d1..0000000
--- a/examples/influenceexplorer/microsoft_contracts
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-from sunlight import influenceexplorer
-
-contracts = influenceexplorer.contracts(
-    vendor_name="Microsoft"
-)
-
-for contract in contracts:
-    print( "%s contract for at least %s to %s" % (
-        contract['transaction_status'],
-        contract['obligatedamount'],
-        contract['descriptionofcontractrequirement']
-    ))
diff --git a/examples/influenceexplorer/nancy_givers b/examples/influenceexplorer/nancy_givers
deleted file mode 100755
index 980434e..0000000
--- a/examples/influenceexplorer/nancy_givers
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-from sunlight import influenceexplorer
-
-givers = influenceexplorer.entities( search="Nancy Pelosi" )
-
-for entity in givers:
-    print( "A(n) %s named %s gave %s" % (
-        entity['type'],
-        entity['name'],
-        entity['total_received']
-    ))
diff --git a/examples/openstates/md_cttys b/examples/openstates/md_cttys
index 6c4fb72..306a637 100755
--- a/examples/openstates/md_cttys
+++ b/examples/openstates/md_cttys
@@ -7,4 +7,4 @@ from sunlight import openstates
 
 md_cttys = openstates.committees( state='md', chamber='upper' )
 for ctty in md_cttys:
-        print( "%s (%s)" % ( ctty['committee'], ctty['chamber'] ))
+    print( "%s (%s)" % ( ctty['committee'], ctty['chamber'] ))
diff --git a/examples/paging b/examples/paging
new file mode 100755
index 0000000..60434d2
--- /dev/null
+++ b/examples/paging
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+from __future__ import print_function
+
+import logging
+
+from sunlight import congress, capitolwords
+from sunlight.pagination import PagingService, logger
+
+
+logger.setLevel(logging.DEBUG)
+
+# try to create a paging service with an unpageable one
+try:
+    capitolwords = PagingService(capitolwords)
+except ValueError, ve:
+    print('ValueError: %s' % ve.message)
+
+# create a pageable service
+congress = PagingService(congress)
+
+print(len(list(congress.legislators(limit=1000)))) # page more than known results
+print(len(list(congress.legislators(limit=5))))    # page less than a single page
+print(len(list(congress.legislators(limit=55))))   # page more than a single page
+
+# bypass unpageable methods
+print(len(congress.all_legislators_in_office()))
+
+# page from an arbitrary page
+print(len(list(congress.legislators(limit=100, page=3))))
diff --git a/setup.py b/setup.py
index daba4c6..7526fdf 100755
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,10 @@ long_description = open('README.rst').read()
 setup(
     name       = __appname__,
     version    = __version__,
-    packages   = [ 'sunlight', 'sunlight.services' ],
+    packages   = ['sunlight', 'sunlight.services'],
+
+    install_requires = ['clint'],
+    entry_points     = {'console_scripts': ['sunlight = sunlight.cli:main']},
 
     author       = "Paul Tagliamonte",
     author_email = "paultag@sunlightfoundation.com",
@@ -20,5 +23,12 @@ setup(
     license          = "BSD",
     url              = "https://github.com/sunlightlabs/python-sunlight",
 
-    platforms        = ['any']
+    platforms        = ['any'],
+
+    classifiers      = ['Programming Language :: Python',
+                        'Programming Language :: Python :: 2',
+                        'Programming Language :: Python :: 2.7',
+                        'Programming Language :: Python :: 3',
+                        'Programming Language :: Python :: 3.3',
+                        'Programming Language :: Python :: 3.4'],
 )
diff --git a/sunlight.egg-info/PKG-INFO b/sunlight.egg-info/PKG-INFO
index 4c27e3d..b65f74e 100644
--- a/sunlight.egg-info/PKG-INFO
+++ b/sunlight.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: sunlight
-Version: 1.1.5
+Version: 1.2.9
 Summary: Unified Sunlight API bindings
 Home-page: https://github.com/sunlightlabs/python-sunlight
 Author: Paul Tagliamonte
@@ -30,7 +30,7 @@ Description: python-sunlight
             echo "API_KEY" > ~/.sunlight.key
         
         where `API_KEY` is actually the string of letters and numbers that was emailed
-        to you. Actually putting `API_KEY` into this file will most likely result in 
+        to you. Actually putting `API_KEY` into this file will most likely result in
         an error from the server. You can confirm they key with the following command:
         
             cat ~/.sunlight.key
@@ -42,10 +42,10 @@ Description: python-sunlight
         
         Basic usage and some brief examples can be found on
         `readthedocs <http://python-sunlight.rtfd.org>`_.
-        If this doens't help, feel free to email for help, ask over IRC in
+        If this doesn't help, feel free to email for help, ask over IRC in
         ``#sunlightlabs`` on ``irc.freenode.net``, or open a issue if it's a
         particularly nasty bug (particularly regarding ambiguous documentation, or
-        poorly exposed API methods). 
+        poorly exposed API methods).
         
         License
         *******
@@ -63,3 +63,9 @@ Description: python-sunlight
         setup.py script.
         
 Platform: any
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
diff --git a/sunlight.egg-info/SOURCES.txt b/sunlight.egg-info/SOURCES.txt
index d3f0aff..159ed37 100644
--- a/sunlight.egg-info/SOURCES.txt
+++ b/sunlight.egg-info/SOURCES.txt
@@ -9,23 +9,23 @@ docs/source/conf.py
 docs/source/index.rst
 docs/source/services/capitolwords.rst
 docs/source/services/congress.rst
+docs/source/services/congress_deprecated.rst
 docs/source/services/openstates.rst
+docs/source/sunlight/cache.rst
 docs/source/sunlight/config.rst
 docs/source/sunlight/errors.rst
+docs/source/sunlight/pagination.rst
 docs/source/sunlight/service.rst
+examples/paging
 examples/capitolwords/gingrich_words
 examples/capitolwords/internet_dates
 examples/capitolwords/july_2010_phrases
 examples/capitolwords/legislators_who_love_free_market
 examples/capitolwords/obama_records
-examples/congress/get_nacy
+examples/congress/who_is_my_rep
+examples/congress_deprecated/get_nacy
 examples/cross-api/death-metal
 examples/cross-api/free_market_lovers
-examples/influenceexplorer/contrib_to_mikulski
-examples/influenceexplorer/homeland_security_grants
-examples/influenceexplorer/lobby_for_pfizer
-examples/influenceexplorer/microsoft_contracts
-examples/influenceexplorer/nancy_givers
 examples/openstates/ca_bill_lookup
 examples/openstates/ca_dems
 examples/openstates/get_json_vt_agro
@@ -35,15 +35,21 @@ examples/openstates/md_cttys
 examples/openstates/tx_events
 examples/openstates/vt_agro_bills
 sunlight/__init__.py
+sunlight/cache.py
+sunlight/cli.py
 sunlight/config.py
 sunlight/errors.py
+sunlight/pagination.py
 sunlight/service.py
 sunlight.egg-info/PKG-INFO
 sunlight.egg-info/SOURCES.txt
 sunlight.egg-info/dependency_links.txt
+sunlight.egg-info/entry_points.txt
+sunlight.egg-info/requires.txt
 sunlight.egg-info/top_level.txt
 sunlight/services/__init__.py
 sunlight/services/capitolwords.py
 sunlight/services/congress.py
-sunlight/services/influenceexplorer.py
+sunlight/services/congress_deprecated.py
+sunlight/services/opencivic.py
 sunlight/services/openstates.py
\ No newline at end of file
diff --git a/sunlight.egg-info/entry_points.txt b/sunlight.egg-info/entry_points.txt
new file mode 100644
index 0000000..7496785
--- /dev/null
+++ b/sunlight.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+sunlight = sunlight.cli:main
+
diff --git a/sunlight.egg-info/requires.txt b/sunlight.egg-info/requires.txt
new file mode 100644
index 0000000..757dc7e
--- /dev/null
+++ b/sunlight.egg-info/requires.txt
@@ -0,0 +1 @@
+clint
diff --git a/sunlight/__init__.py b/sunlight/__init__.py
index 4bfe01f..be9abdd 100644
--- a/sunlight/__init__.py
+++ b/sunlight/__init__.py
@@ -11,22 +11,35 @@ fight with finding the right module to use.
 """
 
 __appname__ = "sunlight"
-__version__ = "1.1.5"
+__version__ = "1.2.9"
 
 import sunlight.services.openstates
 import sunlight.services.capitolwords
 import sunlight.services.congress
-import sunlight.services.influenceexplorer
+import sunlight.services.opencivic
+import sunlight.services.congress_deprecated
 
-openstates = sunlight.services.openstates.openstates()
-capitolwords = sunlight.services.capitolwords.capitolwords()
-congress = sunlight.services.congress.congress()
-influenceexplorer = sunlight.services.influenceexplorer.InfluenceExplorer()
+openstates = sunlight.services.openstates.Openstates()
+capitolwords = sunlight.services.capitolwords.CapitolWords()
+opencivic = sunlight.services.opencivic.OpenCivic()
+
+congress = sunlight.services.congress.Congress()
+congress_deprecated = sunlight.services.congress_deprecated.Congress()
 
 import os.path
 import warnings
 import sunlight.config
 import sunlight.service
+import sunlight.cache
+
+
+def available_services():
+    return {
+        'openstates': openstates,
+        'capitolwords': capitolwords,
+        'congress': congress,
+    }
+
 
 def _attempt_to_load_apikey():
     """
@@ -49,8 +62,11 @@ def _attempt_to_load_apikey():
                 sunlight.config.KEY_LOCATION, str(e)))
     try:
         sunlight.config.API_KEY = \
-                os.environ[sunlight.config.KEY_ENVVAR].strip()
+            os.environ[sunlight.config.KEY_ENVVAR].strip()
     except KeyError as e:
         pass
 
 _attempt_to_load_apikey()
+
+
+response_cache = sunlight.cache.response_cache
diff --git a/sunlight/cache.py b/sunlight/cache.py
new file mode 100644
index 0000000..6058e4c
--- /dev/null
+++ b/sunlight/cache.py
@@ -0,0 +1,182 @@
+'''
+.. module:: cache
+
+The cache is disabled by default. Use it like so: ::
+
+    import logging
+    from sunlight import response_cache
+    response_cache.enable('mongo')
+    response_cache.logger.setLevel(logging.DEBUG)
+
+Note: the implementation below doesn't bother with cache expiration.
+Typical use case is caching API calls during an expensive build process.
+'''
+import pickle
+import logging
+import functools
+
+
+backends = {}
+
+
+class _BackendMeta(type):
+    def __new__(meta, name, bases, attrs):
+        cls = type.__new__(meta, name, bases, attrs)
+        backends[name] = cls
+        shortname = name.lower().replace('backend', '')
+        backends[shortname] = cls
+        for nickname in attrs.get('nicknames', []):
+            backends[nickname] = cls
+        return cls
+
+
+class BaseBackend(object):
+    __metaclass__ = _BackendMeta
+
+    def check(self, key):
+        '''Try to return something from the cache.
+        '''
+        raise NotImplementedError()
+
+    def set(self, key, val):
+        '''Set something in the cache.
+        '''
+        raise NotImplementedError()
+
+    def purge(self, *keys):
+        '''Try to purge one or more things from the cache.
+        If keys is empty, purges everything.
+        '''
+        raise NotImplementedError()
+
+
+class MemoryBackend(BaseBackend):
+    '''In-memory cache for API responses.
+    '''
+    nicknames = ['mem', 'locmem', 'localmem']
+
+    def __init__(self):
+        self._cache = {}
+
+    def check(self, key):
+        return self._cache.get(key)
+
+    def set(self, key, val):
+        self._cache[key] = val
+
+    def purge(self, *keys):
+        if not keys:
+            self._cache = {}
+        map(self._cache.pop, keys)
+
+
+class MongoBackend(BaseBackend):
+    '''Mongo cache of API respones.
+    '''
+    def __init__(self):
+        self.mongo = get_mongo()
+
+    def check(self, key):
+        doc = self.mongo.responses.find_one(key)
+        if doc:
+            return doc['v']
+
+    def set(self, key, val):
+        doc = dict(_id=key, v=val)
+        self.mongo.responses.save(doc)
+
+    def purge(self, *keys):
+        if not keys:
+            spec = {}
+        else:
+            spec = {'_id': {'$in': keys}}
+        self.mongo.reponses.remove(spec)
+
+
+class BaseCache(object):
+
+    def __init__(self):
+        self.backend = None
+        self.logger = logging.getLogger('cache')
+        ch = logging.StreamHandler()
+        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+        ch.setFormatter(formatter)
+        self.logger.addHandler(ch)
+
+    def set_backend(self, backend_name):
+        try:
+            self.backend = backends[backend_name]()
+            self.logger.info('Changed cache backend to %r.' % self.backend)
+        except KeyError:
+            raise ValueError('No backend named %r is defined.' % backend_name)
+
+    enable = set_backend
+
+    def disable(self):
+        '''Disable the cache. Will wipe out an in-memory cache.
+        '''
+        self.backend = None
+        self.logger.info('Response caching disabled.')
+
+    def purge(self):
+        '''Wipe out the cache.
+        '''
+        self.logger.info('Purging cache...')
+        self.backend.purge()
+        self.logger.info('...done.')
+
+    def get_key(self, *args, **kwargs):
+        '''Create a cache key based on the input to the wrapped callable.
+        '''
+        raise NotImplementedError()
+
+    def __call__(self, method):
+        '''Returns a class decorator.
+        '''
+        cache = self
+
+        @functools.wraps(method)
+        def memoizer(self, *args, **kwargs):
+            # If no backend is set, do nothing.
+            if cache.backend is None:
+                return method(self, *args, **kwargs)
+            key = cache.get_key(self, *args, **kwargs)
+            val = cache.backend.check(key)
+            if val is None:
+                cache.logger.debug(' MISS %r' % [self, args, kwargs])
+                val = method(self, *args, **kwargs)
+                cache.backend.set(key, val)
+            else:
+                cache.logger.debug(' HIT %r' % [self, args, kwargs])
+            return val
+        return memoizer
+
+
+class ResponseCache(BaseCache):
+    '''Simple cache implementation with pickled strings as cache keys.
+    '''
+
+    def get_key(self, method_self, *args, **kwargs):
+        '''Create a cache key by: pickle.dumps((module, name, args, kwargs))
+        '''
+        name = self.__class__.__name__
+        module = self.__class__.__module__
+        key = pickle.dumps((module, name, args, kwargs))
+        return key
+
+
+response_cache = ResponseCache()
+
+
+def get_mongo():
+    try:
+        import pymongo
+    except ImportError:
+        msg = 'The mongo cache backend requires pymongo.'
+        raise ImportError(msg)
+    from sunlight import config
+    host = getattr(config, 'MONGO_HOST', None)
+    dbname = getattr(config, 'MONGO_DATABASE_NAME', 'pythonsunlight_cache')
+    conn = pymongo.MongoClient(host=host)
+    mongo = getattr(conn, dbname)
+    return mongo
diff --git a/sunlight/cli.py b/sunlight/cli.py
new file mode 100644
index 0000000..706e50b
--- /dev/null
+++ b/sunlight/cli.py
@@ -0,0 +1,64 @@
+import json
+import itertools
+
+from clint import arguments
+from clint.textui import puts, puts_err, indent, colored
+import sunlight
+
+
+def main():
+    args = arguments.Args()
+    services = sunlight.available_services()
+    service = services.get(args.get(0), None)
+
+    def is_exposable_method(object, m):
+        return (not m.startswith('_') and m != 'get' and callable(getattr(service, m, None)))
+
+    if service is not None:
+        available_methods = [
+            m for m in dir(service) if is_exposable_method(service, m)
+        ]
+        if args.get(1) in available_methods:
+
+            params = dict([
+                (f.strip('--'), args.value_after(f)) for f in args.flags.all
+            ])
+            fn_args = [g.split(',') for g in args.grouped.get('_')[2:]]
+            fn_args = list(itertools.chain.from_iterable(fn_args))
+            try:
+                resp = getattr(service, args.get(1))(*fn_args, **params)
+            except Exception as e:
+                error_name = e.__class__.__name__ if e.__class__.__name__ != 'type' else 'Error'
+                puts_err(colored.red("{}:".format(error_name)))
+                with indent(4):
+                    puts_err(colored.yellow(e.message.decode()))
+                return
+            meta = getattr(resp, '_meta', None)
+            if meta:
+                puts(colored.yellow(json.dumps(meta, indent=2)))
+            puts(colored.blue(json.dumps(resp, indent=2) + '\n'))
+        else:
+            help(methods=available_methods)  # missing or invalid method param
+
+    else:
+        help(services=services)  # missing or invalid service parameter
+
+
+def help(services=None, methods=None):
+    puts_err("Usage: sunlight <service> <method> [<args>, ...]")
+
+    if services:
+        puts_err("Available services:")
+        with indent(4):
+            for s in services:
+                puts_err(s)
+
+    if methods:
+        puts_err("Available methods:")
+        with indent(4):
+            for m in methods:
+                puts_err(m)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/sunlight/config.py b/sunlight/config.py
index 0669382..97ad901 100644
--- a/sunlight/config.py
+++ b/sunlight/config.py
@@ -22,14 +22,14 @@ to do their job.
     All Sunlight services share API keys. Nice, right?
 """
 
-API_SIGNUP_PAGE = "http://services.sunlightlabs.com/accounts/register/"
+API_SIGNUP_PAGE = "http://sunlightfoundation.com/api/accounts/register/"
 """
 This is a link to the API Key signup page - so that we can sanely direct people
 to register for a key (if they don't already have one) -- after all, signing up
 for a Sunlight API key is fun for the whole family!
 """
 
-KEY_LOCATION    = "~/.sunlight.key"
+KEY_LOCATION = "~/.sunlight.key"
 """
 This is the location of the api key that's stored on the filesystem. Currently,
 it uses a file directly under a tilde, so that windows users don't have to feel
@@ -37,7 +37,7 @@ as much pain when using the API. Usually this is something like
 ``~/.sunlight.key``
 """
 
-KEY_ENVVAR      = "SUNLIGHT_API_KEY"
+KEY_ENVVAR = "SUNLIGHT_API_KEY"
 """
 This is the name of the ``os.environ`` key to look for. It's usually something
 stupid simple, like ``SUNLIGHT_API_KEY``.
diff --git a/sunlight/errors.py b/sunlight/errors.py
index 1a60e70..90c983c 100644
--- a/sunlight/errors.py
+++ b/sunlight/errors.py
@@ -6,6 +6,7 @@
     :synopsis: Exceptions and Errors
 """
 
+
 class SunlightException(Exception):
     """
     :class:`sunlight.errors.SunlightException` is the base exception,
@@ -31,6 +32,7 @@ class SunlightException(Exception):
         """
         return repr(self.value)
 
+
 class BadRequestException(SunlightException):
     """
     This gets thrown when the underlying url request has recieved an abnormal
@@ -46,6 +48,7 @@ class InvalidRequestException(BadRequestException):
     """
     pass
 
+
 class NoAPIKeyException(SunlightException):
     """
     This gets thrown if the bindings are asked to issue a request, but the
diff --git a/sunlight/pagination.py b/sunlight/pagination.py
new file mode 100644
index 0000000..a62af2c
--- /dev/null
+++ b/sunlight/pagination.py
@@ -0,0 +1,100 @@
+import time
+import logging
+
+logger = logging.getLogger('sunlight.paginator')
+
+
+def pageable(func):
+    func.is_pageable = True
+    return func
+
+
+class PagingService(object):
+    """
+    PagingService wraps normal services and iterates over the results of consecutive API calls. ::
+
+        from sunlight import congress
+        from sunlight.pagination import PagingService
+
+        paging_service = PagingService(congress)
+
+        print(len(list(paging_service.legislators(limit=55))))   # page more than a single page
+
+
+    """
+
+    limit_attr = 'limit'
+    page_attr = 'page'
+    per_page_attr = 'per_page'
+
+    delay = 0.1
+
+    def __init__(self, service=None, delay=None):
+
+        if service:
+            self.service = service
+        else:
+            self.service = self.service_class()
+
+        if not getattr(self.service, 'is_pageable', False):
+            raise ValueError('service must be a pagable service')
+
+        if delay is not None:
+            self.delay = delay
+
+    def __getattr__(self, name):
+
+        attr = getattr(self.service, name, None)
+
+        if callable(attr) and getattr(attr, 'is_pageable', False):
+
+            def pagingfunc(*args, **kwargs):
+
+                count = 0
+
+                page = int(kwargs.get(self.page_attr, 1))
+                per_page = int(kwargs.get(self.per_page_attr, 50))
+                limit = int(kwargs.pop(self.limit_attr, per_page))
+
+                per_page = min(limit, per_page)
+
+                kwargs[self.per_page_attr] = per_page
+                kwargs[self.page_attr] = 1
+
+                stopthepresses = False
+
+                while 1:
+
+                    logger.debug('loading %s page %d' % (name, page))
+
+                    kwargs[self.page_attr] = page
+                    resp = attr(*args, **kwargs)
+
+                    if not resp:
+                        logger.debug('!   %s returned 0 results this iteration, stopping' % name)
+                        break
+
+                    for rec in resp:
+
+                        yield rec
+
+                        count += 1
+
+                        if count >= limit:
+                            logger.debug('!   count exceeded limit, stopping')
+                            stopthepresses = True
+                            break
+
+                    if count % per_page != 0:
+                        logger.debug('!   %s returned less than number of requested results, stopping' % name)
+                        stopthepresses = True
+
+                    if stopthepresses:
+                        break
+
+                    page += 1
+                    time.sleep(self.delay)
+
+            return pagingfunc
+
+        return attr
diff --git a/sunlight/service.py b/sunlight/service.py
index 07e8e83..296c4b8 100644
--- a/sunlight/service.py
+++ b/sunlight/service.py
@@ -12,14 +12,16 @@ import sys
 
 import sunlight.config
 import sunlight.errors
+from sunlight.cache import response_cache
+
 
 if sys.version_info[0] >= 3:
-    from urllib.parse import urlencode
+    from urllib.parse import urlencode, quote
     from urllib.request import urlopen
     from urllib.error import HTTPError
     _str_type = str
 else:
-    from urllib import urlencode
+    from urllib import urlencode, quote
     from urllib2 import urlopen
     from urllib2 import HTTPError
     _str_type = basestring
@@ -27,18 +29,21 @@ else:
 
 def safe_encode(kwargs):
     kwargs = kwargs.copy()
-    for k, v in kwargs.iteritems():
+    for k, v in kwargs.items():
         if isinstance(v, _str_type):
             kwargs[k] = v.encode('utf8')
     return urlencode(kwargs)
 
 
-class Service:
+class Service(object):
     """
     Base class for all the API implementations, as well as a bunch of common
     code on how to actually fetch text over the network.
     """
 
+    is_pageable = False
+
+    @response_cache
     def get(self, top_level_object, **kwargs):
         """
         Get some data from the network - this is where we actually fetch
@@ -59,9 +64,12 @@ class Service:
         """
         if not sunlight.config.API_KEY:
             raise sunlight.errors.NoAPIKeyException(
-"Warning: Missing API Key. please visit " + sunlight.config.API_SIGNUP_PAGE +
-" to register for a key.")
+                "Warning: Missing API Key. please visit " +
+                sunlight.config.API_SIGNUP_PAGE +
+                " to register for a key."
+            )
 
+        top_level_object = list(map(quote, top_level_object))
         url = self._get_url(top_level_object, sunlight.config.API_KEY,
                             **kwargs)
         try:
@@ -78,6 +86,28 @@ class Service:
 
             ex.url = e.geturl()
             ex.message = message
-            ex.code    = code
+            ex.code = code
 
             raise ex
+
+
+class EntityList(list):
+    """
+    EntityList provides an iterable of API entities along with a _meta
+    dictionary that contains information about the query results. This could
+    include result count and pagination details.
+    """
+    def __init__(self, data=[], meta=None):
+        list.__init__(self, data)
+        self._meta = meta
+
+
+class EntityDict(dict):
+    """
+    EntityDict provides a dictionary representation of an API entity along
+    with a _meta dictionary that contains information about the query results.
+    This could include result count and pagination details.
+    """
+    def __init__(self, data={}, meta=None):
+        dict.__init__(self, data)
+        self._meta = meta
diff --git a/sunlight/services/capitolwords.py b/sunlight/services/capitolwords.py
index 8811779..57c85e5 100644
--- a/sunlight/services/capitolwords.py
+++ b/sunlight/services/capitolwords.py
@@ -7,9 +7,10 @@ from sunlight.errors import InvalidRequestException, BadRequestException
 import sunlight.service
 import json
 
-service_url = "http://capitolwords.org/api"
+service_url = "http://capitolwords.org/api/1"
 
-class capitolwords(sunlight.service.Service):
+
+class CapitolWords(sunlight.service.Service):
     """
     Bindings into the `CapitolWords project <http://capitolwords.org>`_.
     Keep in mind, as you use this wrapper, that this is a very thin translation
@@ -34,7 +35,7 @@ class capitolwords(sunlight.service.Service):
         endpoint <http://capitolwords.org/api/#dates.json>`_.
         """
         kwargs['phrase'] = phrase
-        return self.get( "dates", **kwargs )
+        return self.get(["dates"], **kwargs)
 
     def phrases(self, entity_type, entity_value, **kwargs):
         """
@@ -45,10 +46,10 @@ class capitolwords(sunlight.service.Service):
         For a list of arguments see `Capitol Words' phrases.json
         endpoint <http://capitolwords.org/api/#phrases.json>`_.
         """
-        kwargs['entity_type']  = entity_type
+        kwargs['entity_type'] = entity_type
         kwargs['entity_value'] = entity_value
 
-        return self.get( "phrases", **kwargs )
+        return self.get(["phrases"], **kwargs)
 
     def phrases_by_entity(self, entity_type, **kwargs):
         """
@@ -59,10 +60,9 @@ class capitolwords(sunlight.service.Service):
         For a list of arguments see `Capitol Words' phrases/entity.json
         endpoint <http://capitolwords.org/api/#phrases/entity.json>`_.
         """
-        lss = "%s/%s" % ("phrases", entity_type)
-        return self.get( lss, **kwargs )
+        return self.get(["phrases", entity_type], **kwargs)
 
-    def text( self, phrase=None, title=None, **kwargs ):
+    def text(self, phrase=None, title=None, **kwargs):
         """
         Full text-search against Capitol Words data.
 
@@ -85,25 +85,28 @@ class capitolwords(sunlight.service.Service):
             kwargs['phrase'] = phrase
 
         if title:
-            kwargs['title']  = title
+            kwargs['title'] = title
 
-        return self.get( "text", **kwargs )
+        return self.get(["text"], **kwargs)
 
     # API impl methods below
 
-    def _get_url( self, obj, apikey, **kwargs ):
+    def _get_url(self, pathparts, apikey, **kwargs):
+        # join pieces by slashes and add a trailing slash
+        endpoint_path = "/".join(pathparts)
+
         ret = "%s/%s.json?apikey=%s&%s" % (
             service_url,
-            obj,
+            endpoint_path,
             apikey,
             sunlight.service.safe_encode(kwargs)
         )
         return ret
 
-    def _decode_response( self, response ):
-        ret = json.loads( response )
+    def _decode_response(self, response):
+        ret = json.loads(response)
         if "error" in ret:
-            ex = InvalidRequestException( ret['error'] )
+            ex = InvalidRequestException(ret['error'])
             ex.response = ret
             raise ex
         if "results" in ret:
@@ -111,4 +114,3 @@ class capitolwords(sunlight.service.Service):
             # XXX: Verify this is actually
             #      what we want.
         return ret
-
diff --git a/sunlight/services/congress.py b/sunlight/services/congress_deprecated.py
similarity index 62%
rename from sunlight/services/congress.py
rename to sunlight/services/congress_deprecated.py
index bb6bfdf..d289609 100644
--- a/sunlight/services/congress.py
+++ b/sunlight/services/congress_deprecated.py
@@ -5,7 +5,12 @@
 .. module:: sunlight.services.congress
     :synopsis: Sunlight Congress API Implementation
 
-Sunlight Congress API Implementation inside ``python-sunlight``.
+.. warning::
+    Please avoid using this for new applications, this API has been
+    phased out, and will be taken down at the end of the current
+    congress.
+
+Deprecated Sunlight Congress API Implementation inside ``python-sunlight``.
 """
 
 import sunlight.service
@@ -13,10 +18,12 @@ import json
 
 service_url = "http://services.sunlightlabs.com/api/"
 
+
 def _unpack(resp, key):
-    return [e[key] for e in resp[key+'s']]
+    return [e[key] for e in resp[key + 's']]
 
-class congress(sunlight.service.Service):
+
+class Congress(sunlight.service.Service):
     """
     Bindings into the `Sunlight Congress API
     <http://services.sunlightlabs.com/docs/Sunlight_Congress_API/>`_, an API
@@ -33,22 +40,23 @@ class congress(sunlight.service.Service):
         return _unpack(self.get('legislators.getList', **kwargs),
                        'legislator')
 
-
-    def legislator_search(self, name, threshold=0.9, all_legislators=False):
+    def legislator_search(self, name, threshold=0.9, all_legislators=False,
+                          **kwargs):
         """
         Fuzzy-matching name search against federal legislators.
 
         See documentation at `legislators.search
         <http://services.sunlightlabs.com/docs/congressapi/legislators.search/>`_
         """
-        params = {'name': name, 'threshold': threshold}
+        params = kwargs.copy()
+        params.update({'name': name, 'threshold': threshold})
+
         if all_legislators:
             params['all_legislators'] = 1
-        return _unpack(self.get('legislators.search', **params),
-                       'result')
 
+        return _unpack(self.get('legislators.search', **params), 'result')
 
-    def legislators_for_zip(self, zipcode):
+    def legislators_for_zip(self, zipcode, **kwargs):
         """
         Query for all legislators representing a given ZIP code.
 
@@ -59,71 +67,94 @@ class congress(sunlight.service.Service):
         See documentation at `legislators.allForZip
         <http://services.sunlightlabs.com/docs/congressapi/legislators.allForZip/>`_
         """
-        return _unpack(self.get('legislators.allForZip', zip=zipcode),
-                       'legislator'
-                      )
+        params = kwargs.copy()
+        params.update({
+            "zip": zipcode
+        })
+        return _unpack(self.get('legislators.allForZip', **params),
+                       'legislator')
 
-    def legislators_for_lat_lon(self, latitude, longitude):
+    def legislators_for_lat_lon(self, latitude, longitude, **kwargs):
         """
         Query for all legislators representing an given location.
 
         See documentation at `legislators.allForLatLong
         <http://services.sunlightlabs.com/docs/congressapi/legislators.allForLatLong/>`_
         """
-        return _unpack(self.get('legislators.allForLatLong', latitude=latitude,
-                                longitude=longitude), 'legislator')
+        params = kwargs.copy()
+        params.update({
+            "latitude": latitude,
+            "longitude": longitude
+        })
+        return _unpack(self.get('legislators.allForLatLong', **params),
+                       'legislator')
 
-    def districts_for_zip(self, zipcode):
+    def districts_for_zip(self, zipcode, **kwargs):
         """
         Query for all congressional districts overlapping a zip code.
 
         See documentation at `districts.getDistrictFromLatLong
         <http://services.sunlightlabs.com/docs/congressapi/districts.getDistrictFromLatLong/>`_
         """
-        return _unpack(self.get('districts.getDistrictsFromZip', zip=zipcode),
-                       'district'
-                      )
+        params = kwargs.copy()
+        params.update({
+            "zip": zipcode
+        })
+        return _unpack(self.get('districts.getDistrictsFromZip', **params),
+                       'district')
 
-    def districts_for_lat_lon(self, latitude, longitude):
+    def districts_for_lat_lon(self, latitude, longitude, **kwargs):
         """
         Query for all congressional districts containing a given location.
 
         See documentation at `districts.getDistrictFromLatLong
         <http://services.sunlightlabs.com/docs/congressapi/districts.getDistrictFromLatLong/>`_
         """
-        return _unpack(self.get('districts.getDistrictFromLatLong',
-                                latitude=latitude, longitude=longitude),
-                       'district'
-                      )
+        params = kwargs.copy()
+        params.update({
+            "latitude": latitude,
+            "longitude": longitude
+        })
+        return _unpack(self.get('districts.getDistrictFromLatLong', **params),
+                       'district')
 
-    def committees(self, chamber):
+    def committees(self, chamber, **kwargs):
         """
         Query for all committees for a chamber.  (House|Senate|Joint)
 
         See documentation at `committees.getList
         <http://services.sunlightlabs.com/docs/congressapi/committees.getList/>`_
         """
-        return _unpack(self.get('committees.getList', chamber=chamber),
+        params = kwargs.copy()
+        params.update({"chamber": chamber})
+        return _unpack(self.get('committees.getList', **params),
                        'committee')
 
-    def committee_detail(self, id):
+    def committee_detail(self, committee_id, **kwargs):
         """
         Query for all details for a committee, including members.
 
         See documentation at `committees.get
         <http://services.sunlightlabs.com/docs/congressapi/committees.get/>`_
         """
-        return self.get('committees.get', id=id)['committee']
+        params = kwargs.copy()
+        params.update({"id": committee_id})
+        # We can't use _unpack since top level is `committee' not committees
+        return self.get('committees.get', **params)['committee']
 
-    def committees_for_legislator(self, bioguide_id):
+    def committees_for_legislator(self, bioguide_id, **kwargs):
         """
         Query for all details for all of a legislator's committee assignments.
 
         See documentation at `committees.allForLegislator
         <http://services.sunlightlabs.com/docs/congressapi/committees.allForLegislator/>`_
         """
-        return _unpack(self.get('committees.allForLegislator',
-                                bioguide_id=bioguide_id), 'committee')
+        params = kwargs.copy()
+        params.update({
+            "bioguide_id": bioguide_id
+        })
+        return _unpack(self.get('committees.allForLegislator', **params),
+                       'committee')
 
     # implementation methods
     def _get_url(self, obj, apikey, **kwargs):
diff --git a/sunlight/services/influenceexplorer.py b/sunlight/services/influenceexplorer.py
deleted file mode 100644
index 6d06dce..0000000
--- a/sunlight/services/influenceexplorer.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright (c) Sunlight Labs, 2012 under the terms and conditions
-# of the LICENSE file.
-
-"""
-.. module:: sunlight.services.influenceexplorer
-    :synopsis: InfluenceExplorer API Implementation
-
-InfluenceExplorer API Implementation inside ``python-sunlight``.
-"""
-
-from sunlight.errors import InvalidRequestException
-
-import sunlight.service
-import json
-
-service_url = "http://transparencydata.com/api/1.0"
-
-class InfluenceExplorer(sunlight.service.Service):
-    """
-    Bindings into the `InfluenceExplorer <http://influenceexplorer.com/>`_
-    project. Be careful to note that this is all a very thing layer over the
-    actual API it's self, so be sure to consult the
-    `docs <http://data.influenceexplorer.com/api/>`_ when in doubt over a
-    method's usage.
-    """
-
-    def contributions(self, **kwargs):
-        """
-        Query the InfluenceExplorer server for information regarding political
-        campaign contributions.
-
-        The docs for this method can be found in the
-        `docs <http://data.influenceexplorer.com/api/contributions/>`_.
-        """
-        return self.get("contributions", **kwargs)
-
-    def lobbying(self, **kwargs):
-        """
-        Query the InfluenceExplorer server for information regarding federal
-        lobbying.
-
-        The docs for this method can be found in the
-        `docs <http://data.influenceexplorer.com/api/lobbying/>`_
-        """
-        return self.get("lobbying", **kwargs)
-
-    def grants(self, **kwargs):
-        """
-        Query the InfluenceExplorer server for information regarding federal
-        grants.
-
-        The docs for this method can be found in the
-        `docs <http://data.influenceexplorer.com/api/grants/>`_
-        """
-        return self.get("grants", **kwargs)
-
-    def contracts(self, **kwargs):
-        """
-        Query the InfluenceExplorer server for information regarding federal
-        contracts.
-
-        The docs for this method can be found in the
-        `docs <http://data.influenceexplorer.com/api/contracts/>`_
-        """
-        return self.get("contracts", **kwargs)
-
-    def entities(self, **kwargs):
-        """
-        Query the InfluenceExplorer server for information regarding aggregate
-        contributions by a given entity.
-
-        It sounds much more confusing then it really is.
-
-        The docs for this method can be found in the
-        `docs <http://data.influenceexplorer.com/api/aggregates/contributions/>`_
-        """
-        return self.get("entities", **kwargs)
-
-    # API impl methods below
-
-    def _get_url( self, obj, apikey, **kwargs ):
-        return "%s/%s?apikey=%s&%s" % (
-            service_url,
-            obj,
-            apikey,
-            sunlight.service.safe_encode(kwargs)
-        )
-
-    def _decode_response( self, response ):
-        ret = json.loads( response )
-        if "error" in ret:
-            ex = InvalidRequestException( ret['error'] )
-            ex.response = ret
-            raise ex
-        return ret
-
diff --git a/sunlight/services/opencivic.py b/sunlight/services/opencivic.py
new file mode 100644
index 0000000..04a61f0
--- /dev/null
+++ b/sunlight/services/opencivic.py
@@ -0,0 +1,80 @@
+# Copyright (c) Sunlight Labs, 2012 under the terms and conditions
+# of the LICENSE file.
+
+import sunlight.service
+from sunlight.errors import BadRequestException
+from sunlight.service import EntityDict
+import json
+
+module_name = "opencivic"
+service_url = "http://api.opencivicdata.org"
+
+
+class OpenCivic(sunlight.service.Service):
+
+    def get_list(self, *args, **kwargs):
+
+        def _q(page, *args, **kwargs):
+            ret = self.get(*args, page=page, **kwargs)
+            page = ret['meta']['page']
+            total_pages = ret['meta']['max_page']
+            return ret, (page < total_pages)
+
+        page = 0
+        if 'page' in kwargs:
+            page = kwargs.pop('page')
+
+        has_more = True
+        while has_more:
+            ret, has_more = _q(page, *args, **kwargs)
+            page = ret['meta']['page']
+            for entry in ret['results']:
+                yield EntityDict(data=entry, meta=ret['meta'])
+
+    def get_object(self, *args, **kwargs):
+        ret = self.get(*args, **kwargs)  # In case we get meta eventually.
+        return EntityDict(data=ret)
+
+    def jurisdictions(self, **kwargs):
+        return self.get_list(["jurisdictions"], **kwargs)
+
+    def divisions(self, **kwargs):
+        return self.get_list(["divisions"], **kwargs)
+
+    def organizations(self, **kwargs):
+        return self.get_list(["organizations"], **kwargs)
+
+    def people(self, **kwargs):
+        return self.get_list(["people"], **kwargs)
+
+    def bills(self, **kwargs):
+        return self.get_list(["bills"], **kwargs)
+
+    def votes(self, **kwargs):
+        return self.get_list(["votes"], **kwargs)
+
+    def events(self, **kwargs):
+        return self.get_list(["events"], **kwargs)
+
+    def info(self, ocd_id, **kwargs):
+        return self.get_object([ocd_id], **kwargs)
+
+    def _get_url(self, objs, apikey, **kwargs):
+        # Gate for any None's in the query. This is usually a problem.
+        if None in objs:
+            raise BadRequestException("`None' passed to the URL encoder (%s)" %
+                                      (str(objs)))
+
+        # join pieces by slashes and add a trailing slash
+        object_path = "/".join(objs)
+        object_path += "/"
+
+        return "%s/%s?apikey=%s&%s" % (
+            service_url,
+            object_path,
+            apikey,
+            sunlight.service.safe_encode(kwargs)
+        )
+
+    def _decode_response(self, response):
+        return json.loads(response)
diff --git a/sunlight/services/openstates.py b/sunlight/services/openstates.py
index 1f5d056..735e8b1 100644
--- a/sunlight/services/openstates.py
+++ b/sunlight/services/openstates.py
@@ -8,7 +8,8 @@ import json
 module_name = "openstates"
 service_url = "http://openstates.org/api/v1"
 
-class openstates(sunlight.service.Service):
+
+class Openstates(sunlight.service.Service):
     """
     Bindings into the `Open States API <http://openstates.org/api/>`_. Keep in
     mind this is a thin wrapper around the API so the API documentation is the
@@ -62,7 +63,7 @@ class openstates(sunlight.service.Service):
         The fields and keyword arguments can be found on the
         `Open States Bill API docs <http://openstates.org/api/bills/>`_.
         """
-        lss = ["bills", state, session]
+        lss = ["bills", state, str(session)]
         if chamber:
             lss.append(chamber)
         lss.append(bill_id)
@@ -75,7 +76,7 @@ class openstates(sunlight.service.Service):
         The fields and keyword arguments can be found on the
         `Legislator API docs <http://openstates.org/api/legislators/>`_.
         """
-        return self.get( [ "legislators"], **kwargs )
+        return self.get(["legislators"], **kwargs)
 
     def legislator_detail(self, leg_id, **kwargs):
         """
@@ -98,7 +99,7 @@ class openstates(sunlight.service.Service):
         See the Open States documentation for examples of `Legislator Geo
         Lookup <http://openstates.org/api/legislators/#geo-lookup>`_.
         """
-        kwargs['lat']  = latitude
+        kwargs['lat'] = latitude
         kwargs['long'] = longitude
         return self.get(["legislators", "geo"], **kwargs)
 
@@ -132,7 +133,7 @@ class openstates(sunlight.service.Service):
         See the Open States' site for details on the
         `Event API <http://openstates.org/api/events/>`_.
         """
-        return self.get([ "events" ], **kwargs)
+        return self.get(["events"], **kwargs)
 
     def event_detail(self, event_id, **kwargs):
         """
@@ -143,8 +144,8 @@ class openstates(sunlight.service.Service):
         See the Open States' site for details on the
         `Event API Fields <http://openstates.org/api/events/#event-fields>`_.
         """
-        lss = [ "events", event_id ]
-        return self.get( lss, **kwargs )
+        lss = ["events", event_id]
+        return self.get(lss, **kwargs)
 
     def districts(self, state, chamber=None, **kwargs):
         """
@@ -185,7 +186,7 @@ class openstates(sunlight.service.Service):
 
         # join pieces by slashes and add a trailing slash
         object_path = "/".join(objs)
-        object_path +=  "/"
+        object_path += "/"
 
         return "%s/%s?apikey=%s&%s" % (
             service_url,

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.2.9.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.2.9.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.2.9.egg-info/entry_points.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.2.9.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.2.9.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight/cache.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight/cli.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight/pagination.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight/services/congress_deprecated.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight/services/opencivic.py
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/_modules/sunlight/cache.html
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/_modules/sunlight/pagination.html
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/_sources/services/congress_deprecated.rst.txt
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/_sources/sunlight/cache.rst.txt
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/_sources/sunlight/pagination.rst.txt
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/services/congress_deprecated.html
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/sunlight/cache.html
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/sunlight/pagination.html
-rwxr-xr-x  root/root   /usr/bin/sunlight
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/congress/who_is_my_rep
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/congress_deprecated/get_nacy
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/paging
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/css/badge_only.css -> ../../../../../sphinx_rtd_theme/static/css/badge_only.css
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/css/theme.css -> ../../../../../sphinx_rtd_theme/static/css/theme.css
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-Bold.ttf -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-Bold.ttf
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-Bold.woff2 -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-Bold.woff2
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-BoldItalic.ttf -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-BoldItalic.ttf
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-BoldItalic.woff2 -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-BoldItalic.woff2
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-Italic.ttf -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-Italic.ttf
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-Italic.woff2 -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-Italic.woff2
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-Regular.ttf -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-Regular.ttf
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/Lato-Regular.woff2 -> ../../../../../sphinx_rtd_theme/static/fonts/Lato-Regular.woff2
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/RobotoSlab-Bold.woff2 -> ../../../../../sphinx_rtd_theme/static/fonts/RobotoSlab-Bold.woff2
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/RobotoSlab-Regular.woff2 -> ../../../../../sphinx_rtd_theme/static/fonts/RobotoSlab-Regular.woff2
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/fontawesome-webfont.eot -> ../../../../../sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/fontawesome-webfont.svg -> ../../../../../sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/fontawesome-webfont.ttf -> ../../../../../sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/fontawesome-webfont.woff -> ../../../../../sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/fonts/fontawesome-webfont.woff2 -> ../../../../../sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff2
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/js/theme.js -> ../../../../../sphinx_rtd_theme/static/js/theme.js

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.1.5.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.1.5.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight-1.1.5.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/sunlight/services/influenceexplorer.py
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/_static/classic.css
-rw-r--r--  root/root   /usr/share/doc/python-sunlight-doc/html/_static/default.css
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/congress/get_nacy
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/influenceexplorer/contrib_to_mikulski
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/influenceexplorer/homeland_security_grants
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/influenceexplorer/lobby_for_pfizer
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/influenceexplorer/microsoft_contracts
-rwxr-xr-x  root/root   /usr/share/doc/python-sunlight-doc/examples/influenceexplorer/nancy_givers
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/sidebar.js -> ../../../../javascript/sphinxdoc/1.0/sidebar.js
lrwxrwxrwx  root/root   /usr/share/doc/python-sunlight-doc/html/_static/sphinx_highlight.js -> ../../../../javascript/sphinxdoc/1.0/sphinx_highlight.js

Control files of package python-sunlight-doc: lines which differ (wdiff format)

  • Depends: libjs-sphinxdoc (>= 5.2) 5.0), sphinx-rtd-theme-common (>= 1.2.0~rc1+dfsg)

Control files of package python3-sunlight: lines which differ (wdiff format)

  • Depends: python3-clint, python3:any

More details

Full run details