Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 93877b8302be18e255b3a71bd54d7f79030c6153 @robhudson committed Oct 15, 2010
Showing with 245 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +8 −0 CHANGES.rst
  3. +27 −0 LICENSE.txt
  4. +4 −0 MANIFEST.in
  5. +62 −0 README.rst
  6. +6 −0 pivotal/__init__.py
  7. +35 −0 pivotal/anyetree.py
  8. +56 −0 pivotal/pivotal.py
  9. +20 −0 pivotal/tests.py
  10. +26 −0 setup.py
@@ -0,0 +1 @@
+*.pyc
@@ -0,0 +1,8 @@
+CHANGES
+=======
+
+0.1.0 (2010-10-15)
+------------------
+
+- Initial public release.
+
@@ -0,0 +1,27 @@
+Copyright (c) 2010, Rob Hudson
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of the author nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,4 @@
+include CHANGES.rst
+include LICENSE.txt
+include MANIFEST.in
+include README.rst
@@ -0,0 +1,62 @@
+==========
+Pivotal-Py
+==========
+
+Pivotal-Py is a very thin wrapper built on top of `httplib2` to make requests
+against the `Pivotal Tracker API`_.
+
+.. _Pivotal Tracker API: http://www.pivotaltracker.com/help/api
+
+Installation
+============
+
+Install from PyPI with ``easy_install`` or ``pip``::
+
+ pip install pivotal-py
+
+Dependencies
+------------
+
+Pivotal-Py requires `httplib2`_ 0.6.0 or later.
+
+.. _httplib2: http://pypi.python.org/pypi/httplib2
+
+Usage
+=====
+
+This Pivotal Tracker API client uses the token to authenticate with Pivotal
+Tracker. You can get a token by going to your user profile and creating an API
+token.
+
+Once you have an API token making requests against the APIs follows a simple
+pattern of chainable methods, for example::
+
+ from pivotal import Pivotal
+
+ pv = Pivotal('TOKEN')
+
+ # Assuming a base URL of 'http://www.pivotaltracker.com/services/v3/'
+
+ # Perform a GET at /projects
+ etree = pv.projects().get()
+
+Any positional arguments get pushed onto the URL::
+
+ # Perform a GET at /projects/[id]/stories where [id] is a project ID.
+ etree = pv.projects(id).stories().get()
+
+Any keyword arguments get mapped to the URL query string and appended at the
+end of the URL::
+
+ # Perform a GET at /projects/[id]/stories and filter stories by those that
+ # are not started.
+ etree = pv.projects(id).stories(filter='state:unstarted').get()
+ # Results in a URL of: /projects/[id]/stories?filter=state%3Aunstarted
+
+Note: POSTs are currently not implemented, but the plan is to implement a
+``post`` method that takes a body argument which is the body of the POST::
+
+ # TODO: POST to /projects to create a new project
+ etree = pv.projects().post(xml_body)
+
+
@@ -0,0 +1,6 @@
+from pivotal import Pivotal
+
+VERSION = (0, 1, 0)
+__version__ = '.'.join(map(str, VERSION))
+
+
@@ -0,0 +1,35 @@
+"""
+Get an Etree library. Usage::
+
+ >>> from anyetree import etree
+
+Returns some etree library. Looks for (in order of decreasing preference):
+
+ * ``lxml.etree`` (http://cheeseshop.python.org/pypi/lxml/)
+ * ``xml.etree.cElementTree`` (built into Python 2.5)
+ * ``cElementTree`` (http://effbot.org/zone/celementtree.htm)
+ * ``xml.etree.ElementTree`` (built into Python 2.5)
+ * ``elementree.ElementTree (http://effbot.org/zone/element-index.htm)
+"""
+
+__all__ = ['etree']
+
+SEARCH_PATHS = [
+ "lxml.etree",
+ "xml.etree.cElementTree",
+ "cElementTree",
+ "xml.etree.ElementTree",
+ "elementtree.ElementTree",
+]
+
+etree = None
+
+for name in SEARCH_PATHS:
+ try:
+ etree = __import__(name, '', '', [''])
+ break
+ except ImportError:
+ continue
+
+if etree is None:
+ raise ImportError("No suitable ElementTree implementation found.")
@@ -0,0 +1,56 @@
+from anyetree import etree
+import copy
+import httplib2
+from urllib import urlencode
+
+
+BASE_URL = 'http://www.pivotaltracker.com/services/v3/'
+
+
+class Pivotal(object):
+
+ def __init__(self, token):
+ self.token = token
+ self.path = []
+ self.qs = {}
+
+ def __getattr__(self, method):
+ # Create a new copy of self
+ obj = self.__class__(self.token)
+ obj.path = copy.copy(self.path)
+ obj.qs = copy.copy(self.qs)
+
+ obj.path.append(method)
+ return obj.mock_attr
+
+ def mock_attr(self, *args, **kwargs):
+ """
+ Empty method to call to slurp up args and kwargs.
+
+ `args` get pushed onto the url path.
+ `kwargs` are converted to a query string and appended to the URL.
+ """
+ self.path.extend(args)
+ self.qs.update(kwargs)
+ return self
+
+ @property
+ def url(self):
+ url = BASE_URL + '/'.join(map(str, self.path))
+ if self.qs:
+ url += '?' + urlencode(self.qs)
+ return url
+
+ def get(self):
+ h = httplib2.Http(timeout=15)
+ h.force_exception_to_status_code = True
+ headers = {
+ 'X-TrackerToken': self.token,
+ }
+ response, content = h.request(self.url, headers=headers)
+ return etree.fromstring(content)
+
+ def post(self, body):
+ # TODO: Flesh out POSTs
+ raise NotImplementedError
+
@@ -0,0 +1,20 @@
+import unittest
+
+from pivotal import Pivotal, BASE_URL
+
+class PivotalTest(unittest.TestCase):
+
+ def test_url_strings(self):
+ pv = Pivotal('ABCDEF')
+
+ self.assertEqual(pv.projects().url, BASE_URL + 'projects')
+ self.assertEqual(pv.projects(123).url, BASE_URL + 'projects/123')
+ self.assertEqual(pv.projects('123').url, BASE_URL + 'projects/123')
+ self.assertEqual(pv.projects('123').stories().url,
+ BASE_URL + 'projects/123/stories')
+ self.assertEqual(pv.projects('123').stories(filter='state:unstarted').url,
+ BASE_URL + 'projects/123/stories?filter=state%3Aunstarted')
+
+
+if __name__ == '__main__':
+ unittest.main()
@@ -0,0 +1,26 @@
+from setuptools import setup
+
+
+long_description = open('README.rst').read() + open('CHANGES.rst').read()
+
+setup(
+ name='pivotal-py',
+ version=__import__('pivotal').__version__,
+ description='Thin client for Pivotal Tracker\'s API',
+ long_description=long_description,
+ author='Rob Hudson',
+ author_email='rob@cogit8.org',
+ url='http://github.com/robhudson/pivotal-py/',
+ packages=['pivotal'],
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: WWW/HTTP',
+ ],
+ zip_safe=False,
+)
+

0 comments on commit 93877b8

Please sign in to comment.