[Pkg-puppet-devel] [python-pypuppetdb] 01/01: Imported Upstream version 0.1.1

Jonas Genannt jonas at brachium-system.net
Fri Jul 25 12:18:19 UTC 2014


This is an automated email from the git hooks/post-receive script.

hggh-guest pushed a commit to branch master
in repository python-pypuppetdb.

commit 7d02475132ec086ce138ca53c9e7e982845c90ae
Author: Jonas Genannt <jonas at brachium-system.net>
Date:   Fri Jul 25 14:18:09 2014 +0200

    Imported Upstream version 0.1.1
---
 CHANGELOG.rst                            | 115 +++++++++
 LICENSE                                  | 191 +++++++++++++++
 MANIFEST.in                              |   4 +
 PKG-INFO                                 | 378 ++++++++++++++++++++++++++++
 README.rst                               | 240 ++++++++++++++++++
 pypuppetdb.egg-info/PKG-INFO             | 378 ++++++++++++++++++++++++++++
 pypuppetdb.egg-info/SOURCES.txt          |  19 ++
 pypuppetdb.egg-info/dependency_links.txt |   1 +
 pypuppetdb.egg-info/requires.txt         |   1 +
 pypuppetdb.egg-info/top_level.txt        |   1 +
 pypuppetdb/__init__.py                   | 114 +++++++++
 pypuppetdb/api/__init__.py               | 321 ++++++++++++++++++++++++
 pypuppetdb/api/v2.py                     | 122 +++++++++
 pypuppetdb/api/v3.py                     | 254 +++++++++++++++++++
 pypuppetdb/errors.py                     |  30 +++
 pypuppetdb/package.py                    |   9 +
 pypuppetdb/types.py                      | 408 +++++++++++++++++++++++++++++++
 pypuppetdb/utils.py                      |  43 ++++
 setup.cfg                                |  15 ++
 setup.py                                 |  62 +++++
 20 files changed, 2706 insertions(+)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..1e17d93
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,115 @@
+#########
+Changelog
+#########
+
+0.1.1
+=====
+
+* Fix the license in our ``setup.py``. The license shouldn't be longer than
+  200 characters. We were including the full license tripping up tools like
+  bdist_rpm.
+
+0.1.0
+=====
+Significant changes have been made in this release. The complete v3 API is
+now supported except for query pagination.
+
+Most changes are backwards compatible except for a change in the SSL
+configuration. The previous behaviour was buggy and slightly misleading in
+the names the options took:
+
+* ``ssl`` has been renamed to ``ssl_verify`` and now defaults to ``True``.
+* Automatically use HTTPS if ``ssl_key`` and ``ssl_cert`` are provided.
+
+For additional instructions about getting SSL to work see the Quickstart
+in the documentation.
+
+Deprecation
+------------
+Support for API v2 will be dropped in the 0.2.x release series.
+
+New features
+------------
+
+The following features are **only** supported for **API v3**.
+
+The ``node()`` and ``nodes()`` function have gained the following options:
+
+  * ``with_status=False``
+  * ``unreported=2``
+
+When ``with_status`` is set to ``True`` an additional query will be made using
+the ``events-count`` endpoint scoped to the latest report. This will result in
+an additional ``events`` and ``status`` keys on the node object. ``status``
+will be either of ``changed``, ``unchanged`` or ``failed`` depending on if
+``events`` contains ``successes`` or ``failures`` or none.
+
+By default ``unreported`` is set to ``2``. This is only in effect when
+``with_status`` is set to ``True``. It means that if a node hasn't checked in
+for two hours it will get a ``status`` of ``unreported`` instead.
+
+New endpoints:
+
+  * ``events-count``: ``events_count()``
+  * ``aggregate-event-counts``: ``aggregate_event_counts()``
+  * ``server-time``: ``server_time()``
+  * ``version``: ``current_version()``
+  * ``catalog``: ``catalog()``
+
+New types:
+
+  * ``pypuppetdb.types.Catalog``
+  * ``pypuppetdb.types.Edge``
+
+Changes to types:
+
+  * ``pypuppetdb.types.Node`` now has:
+    * ``status`` defaulting to ``None``
+    * ``events`` defaulting to ``None``
+    * ``unreported_time`` defaulting to ``None``
+
+0.0.4
+=====
+
+Due to a fairly serious bug 0.0.3 was pulled from PyPi minutes after release.
+
+When a bug was fixed to be able to query for all facts we accidentally
+introduced a different bug that caused the ``facts()`` call on a node to
+query for all facts because we were resetting the query.
+
+* Fix a bug where ``node.facts()`` was causing us to query all facts because
+  the query to scope our request was being reset.
+
+0.0.3
+=====
+
+With the introduction of PuppetDB 1.5 a new API version, v3, was also
+introduced. In that same release the old ``/experimental`` endpoints
+were removed, meaning that as of PuppetDB 1.5 with the v2 API you can
+no longer get access to reports or events.
+
+In light of this the support for the experimental endpoints has been
+completely removed from pypuppetdb. As of this release you can only get
+to reports and/or events through v3 of the API.
+
+This release includes preliminary support for the v3 API. Everything that
+could be done with v2 plus the experimental endpoints is now possible on
+v3. However, more advanced funtionality has not yet been implemented. That
+will be the focus of the next release.
+
+* Removed dependency on pytz.
+* Fixed the behaviour of ``facts()`` and ``resources()``. We can now
+  correctly query for all facts or resources.
+* Fixed an issue with catalog timestampless nodes.
+* Pass along the ``timeout`` option to ``connect()``.
+* Added preliminary PuppetDB API v3 support.
+* Removed support for the experimental endpoints.
+* The ``connect()`` method defaults to API v3 now.
+
+0.0.2
+=====
+* Fix a bug in ``setup.py`` preventing successful installation.
+
+0.0.1
+=====
+Initial release. Implements most of the v2 API.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..685b7b0
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include README.rst
+include CHANGELOG.rst
+include LICENSE
+include requirements.txt
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..4e78d5b
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,378 @@
+Metadata-Version: 1.1
+Name: pypuppetdb
+Version: 0.1.1
+Summary: Library for working with the PuppetDB REST API.
+Home-page: https://github.com/nedap/pypuppetdb
+Author: Daniele Sluijters
+Author-email: daniele.sluijters+pypi at gmail.com
+License: Apache License 2.0
+Description: ##########
+        pypuppetdb
+        ##########
+        
+        .. image:: https://api.travis-ci.org/nedap/pypuppetdb.png
+           :target: https://travis-ci.org/nedap/pypuppetdb
+        
+        .. image:: https://coveralls.io/repos/nedap/pypuppetdb/badge.png
+           :target: https://coeralls.io/r/nedap/pypuppetdb
+        
+        .. image:: https://pypip.in/d/pypuppetdb/badge.png
+           :target: https://crate.io/packages/pypuppetdb
+        
+        .. image:: https://pypip.in/v/pypuppetdb/badge.png
+           :target: https://crate.io/packages/pypuppetdb
+        
+        pypuppetdtb is a library to work with PuppetDB's REST API. It is implemented
+        using the `requests`_ library.
+        
+        .. _requests: http://docs.python-requests.org/en/latest/
+        
+        This library is a thin wrapper around the REST API providing some convinience
+        functions and objects to request and hold data from PuppetDB.
+        
+        To use this library you will need:
+            * Python 2.6 or 2.7
+            * Python 3.3
+        
+        Installation
+        ============
+        
+        You can install this package from source or from PyPi.
+        
+        .. code-block:: bash
+        
+           $ pip install pypuppetdb
+        
+        .. code-block:: bash
+        
+           $ git clone https://github.com/nedap/pypuppetdb
+           $ python setup.py install
+        
+        If you wish to hack on it clone the repository but after that run:
+        
+        .. code-block:: bash
+        
+           $ pip install -r requirements.txt
+        
+        This will install all the runtime requirements of pypuppetdb and the
+        dependencies for the test suite and generation of documentation.
+        
+        Usage
+        =====
+        
+        Once you have pypuppetdb installed you can configure it to connect to PuppetDB
+        and take it from there.
+        
+        Connecting
+        ----------
+        
+        The first thing you need to do is to connect with PuppetDB:
+        
+        .. code-block:: python
+        
+           >>> from pypuppetdb import connect
+           >>> db = connect()
+        
+        Nodes
+        -----
+        
+        The following will return a generator object yielding Node objects for every
+        returned node from PuppetDB.
+        
+        .. code-block:: python
+        
+           >>> nodes = db.nodes()
+           >>> for node in nodes:
+           >>>   print(node)
+           host1
+           host2
+           ...
+        
+        To query a single node the singular `node()` can be used:
+        
+        .. code-block:: python
+        
+            >>> node = db.node('hostname')
+            >>> print(node)
+            hostname
+        
+        Node scope
+        ~~~~~~~~~~
+        
+        The Node objects are a bit more special in that they can query for facts and
+        resources themselves. Using those methods from a node object will automatically
+        add a query to the request scoping the request to the node.
+        
+        .. code-block:: python
+        
+           >>> node = db.node('hostname')
+           >>> print(node.fact('osfamily'))
+           osfamily/hostname
+        
+        Facts
+        -----
+        
+        .. code-block:: python
+        
+           >>> facts = db.facts('osfamily')
+           >>> for fact in facts:
+           >>>   print(fact)
+           osfamily/host1
+           osfamily/host2
+        
+        That queries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+        one per node this fact is known for.
+        
+        Resources
+        ---------
+        
+        .. code-block:: python
+        
+           >>> resources = db.resources('file')
+        
+        Will return a generator object containing all file resources you're managing
+        across your infrastructure. This is probably a bad idea if you have a big
+        number of nodes as the response will be huge.
+        
+        Catalogs
+        ---------
+        
+        .. code-block:: python
+        
+           >>> catalog = db.catalog('hostname')
+           >>> for res in catalog.get_resources():
+           >>>     print(res)
+        
+        Will return a Catalog object with the latest Catalog of the definded host. This
+        catalog contains the defined Resources and Edges.
+        
+        Getting Help
+        ============
+        This project is still very new so it's not inconceivable you'll run into
+        issues.
+        
+        For bug reports you can file an `issue`_. If you need help with something
+        feel free to hit up `@daenney`_ by e-mail or find him on IRC. He can usually
+        be found on `IRCnet`_ and `Freenode`_ and idles in #puppet.
+        
+        There's now also the #puppetboard channel on `Freenode`_ where we hang out
+        and answer questions related to pypuppetdb and Puppetboard.
+        
+        .. _issue: https://github.com/nedap/pypuppetdb/issues
+        .. _ at daenney: https://github.com/daenney
+        .. _IRCnet: http://www.ircnet.org
+        .. _Freenode: http://freenode.net
+        
+        Documentation
+        =============
+        API documentation is automatically generated from the docstrings using
+        Sphinx's autodoc feature. 
+        
+        Documentation will automatically be rebuilt on every push thanks to the
+        Read The Docs webhook. You can `find it here`_.
+        
+        .. _find it here: https://pypuppetdb.readthedocs.org/en/latest/
+        
+        You can build the documentation manually by doing:
+        
+        .. code-block:: bash
+        
+           $ cd docs
+           $ make html
+        
+        Doing so will only work if you have Sphinx installed, which you can acheive
+        through:
+        
+        .. code-block:: bash
+        
+           $ pip install -r requirements.txt
+        
+        Contributing
+        ============
+        
+        We welcome contributions to this library. However, there are a few ground
+        rules contributors should be aware of.
+        
+        License
+        -------
+        This project is licensed under the Apache v2.0 License. As such, your
+        contributions, once accepted, are automatically covered by this license.
+        
+        Commit messages
+        ---------------
+        Write decent commit messages. Don't use swear words and refrain from
+        uninformative commit messages as 'fixed typo'.
+        
+        The preferred format of a commit message:
+        
+        ::
+        
+            docs/quickstart: Fixed a typo in the Nodes section.
+        
+            If needed, elaborate further on this commit. Feel free to write a
+            complete blog post here if that helps us understand what this is
+            all about.
+        
+            Fixes #4 and resolves #2.
+        
+        If you'd like a more elaborate guide on how to write and format your commit
+        messages have a look at this post by `Tim Pope`_.
+        
+        .. _Tim Pope: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+        
+        Tests
+        -----
+        Commits are expected to contain tests or updates to tests if they add to or
+        modify the current behaviour.
+        
+        The test suite is powered by `pytest`_ and requires `pytest`_, `pytest-pep8`_,
+        `httpretty`_ and `pytest-httpretty`_ which will be installed for you if you
+        run:
+        
+        .. code-block:: bash
+        
+           $ pip install -r requirements.txt
+        
+        .. _pytest: http://pytest.org/latest/
+        .. _pytest-pep8: https://pypi.python.org/pypi/pytest-pep8
+        .. _httpretty: https://pypi.python.org/pypi/httpretty/
+        .. _pytest-httpretty: https://github.com/papaeye/pytest-httpretty
+        
+        To run the unit tests (the ones that don't require a live PuppetDB):
+        
+        .. code-block:: bash
+           
+           $ py.test -v -m unit
+        
+        If the tests pass, you're golden. If not we'll have to figure out why and
+        fix that. Feel free to ask for help on this.
+        
+        #########
+        Changelog
+        #########
+        
+        0.1.1
+        =====
+        
+        * Fix the license in our ``setup.py``. The license shouldn't be longer than
+          200 characters. We were including the full license tripping up tools like
+          bdist_rpm.
+        
+        0.1.0
+        =====
+        Significant changes have been made in this release. The complete v3 API is
+        now supported except for query pagination.
+        
+        Most changes are backwards compatible except for a change in the SSL
+        configuration. The previous behaviour was buggy and slightly misleading in
+        the names the options took:
+        
+        * ``ssl`` has been renamed to ``ssl_verify`` and now defaults to ``True``.
+        * Automatically use HTTPS if ``ssl_key`` and ``ssl_cert`` are provided.
+        
+        For additional instructions about getting SSL to work see the Quickstart
+        in the documentation.
+        
+        Deprecation
+        ------------
+        Support for API v2 will be dropped in the 0.2.x release series.
+        
+        New features
+        ------------
+        
+        The following features are **only** supported for **API v3**.
+        
+        The ``node()`` and ``nodes()`` function have gained the following options:
+        
+          * ``with_status=False``
+          * ``unreported=2``
+        
+        When ``with_status`` is set to ``True`` an additional query will be made using
+        the ``events-count`` endpoint scoped to the latest report. This will result in
+        an additional ``events`` and ``status`` keys on the node object. ``status``
+        will be either of ``changed``, ``unchanged`` or ``failed`` depending on if
+        ``events`` contains ``successes`` or ``failures`` or none.
+        
+        By default ``unreported`` is set to ``2``. This is only in effect when
+        ``with_status`` is set to ``True``. It means that if a node hasn't checked in
+        for two hours it will get a ``status`` of ``unreported`` instead.
+        
+        New endpoints:
+        
+          * ``events-count``: ``events_count()``
+          * ``aggregate-event-counts``: ``aggregate_event_counts()``
+          * ``server-time``: ``server_time()``
+          * ``version``: ``current_version()``
+          * ``catalog``: ``catalog()``
+        
+        New types:
+        
+          * ``pypuppetdb.types.Catalog``
+          * ``pypuppetdb.types.Edge``
+        
+        Changes to types:
+        
+          * ``pypuppetdb.types.Node`` now has:
+            * ``status`` defaulting to ``None``
+            * ``events`` defaulting to ``None``
+            * ``unreported_time`` defaulting to ``None``
+        
+        0.0.4
+        =====
+        
+        Due to a fairly serious bug 0.0.3 was pulled from PyPi minutes after release.
+        
+        When a bug was fixed to be able to query for all facts we accidentally
+        introduced a different bug that caused the ``facts()`` call on a node to
+        query for all facts because we were resetting the query.
+        
+        * Fix a bug where ``node.facts()`` was causing us to query all facts because
+          the query to scope our request was being reset.
+        
+        0.0.3
+        =====
+        
+        With the introduction of PuppetDB 1.5 a new API version, v3, was also
+        introduced. In that same release the old ``/experimental`` endpoints
+        were removed, meaning that as of PuppetDB 1.5 with the v2 API you can
+        no longer get access to reports or events.
+        
+        In light of this the support for the experimental endpoints has been
+        completely removed from pypuppetdb. As of this release you can only get
+        to reports and/or events through v3 of the API.
+        
+        This release includes preliminary support for the v3 API. Everything that
+        could be done with v2 plus the experimental endpoints is now possible on
+        v3. However, more advanced funtionality has not yet been implemented. That
+        will be the focus of the next release.
+        
+        * Removed dependency on pytz.
+        * Fixed the behaviour of ``facts()`` and ``resources()``. We can now
+          correctly query for all facts or resources.
+        * Fixed an issue with catalog timestampless nodes.
+        * Pass along the ``timeout`` option to ``connect()``.
+        * Added preliminary PuppetDB API v3 support.
+        * Removed support for the experimental endpoints.
+        * The ``connect()`` method defaults to API v3 now.
+        
+        0.0.2
+        =====
+        * Fix a bug in ``setup.py`` preventing successful installation.
+        
+        0.0.1
+        =====
+        Initial release. Implements most of the v2 API.
+        
+Keywords: puppet puppetdb
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Topic :: Software Development :: Libraries
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..c20d243
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,240 @@
+##########
+pypuppetdb
+##########
+
+.. image:: https://api.travis-ci.org/nedap/pypuppetdb.png
+   :target: https://travis-ci.org/nedap/pypuppetdb
+
+.. image:: https://coveralls.io/repos/nedap/pypuppetdb/badge.png
+   :target: https://coeralls.io/r/nedap/pypuppetdb
+
+.. image:: https://pypip.in/d/pypuppetdb/badge.png
+   :target: https://crate.io/packages/pypuppetdb
+
+.. image:: https://pypip.in/v/pypuppetdb/badge.png
+   :target: https://crate.io/packages/pypuppetdb
+
+pypuppetdtb is a library to work with PuppetDB's REST API. It is implemented
+using the `requests`_ library.
+
+.. _requests: http://docs.python-requests.org/en/latest/
+
+This library is a thin wrapper around the REST API providing some convinience
+functions and objects to request and hold data from PuppetDB.
+
+To use this library you will need:
+    * Python 2.6 or 2.7
+    * Python 3.3
+
+Installation
+============
+
+You can install this package from source or from PyPi.
+
+.. code-block:: bash
+
+   $ pip install pypuppetdb
+
+.. code-block:: bash
+
+   $ git clone https://github.com/nedap/pypuppetdb
+   $ python setup.py install
+
+If you wish to hack on it clone the repository but after that run:
+
+.. code-block:: bash
+
+   $ pip install -r requirements.txt
+
+This will install all the runtime requirements of pypuppetdb and the
+dependencies for the test suite and generation of documentation.
+
+Usage
+=====
+
+Once you have pypuppetdb installed you can configure it to connect to PuppetDB
+and take it from there.
+
+Connecting
+----------
+
+The first thing you need to do is to connect with PuppetDB:
+
+.. code-block:: python
+
+   >>> from pypuppetdb import connect
+   >>> db = connect()
+
+Nodes
+-----
+
+The following will return a generator object yielding Node objects for every
+returned node from PuppetDB.
+
+.. code-block:: python
+
+   >>> nodes = db.nodes()
+   >>> for node in nodes:
+   >>>   print(node)
+   host1
+   host2
+   ...
+
+To query a single node the singular `node()` can be used:
+
+.. code-block:: python
+
+    >>> node = db.node('hostname')
+    >>> print(node)
+    hostname
+
+Node scope
+~~~~~~~~~~
+
+The Node objects are a bit more special in that they can query for facts and
+resources themselves. Using those methods from a node object will automatically
+add a query to the request scoping the request to the node.
+
+.. code-block:: python
+
+   >>> node = db.node('hostname')
+   >>> print(node.fact('osfamily'))
+   osfamily/hostname
+
+Facts
+-----
+
+.. code-block:: python
+
+   >>> facts = db.facts('osfamily')
+   >>> for fact in facts:
+   >>>   print(fact)
+   osfamily/host1
+   osfamily/host2
+
+That queries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+one per node this fact is known for.
+
+Resources
+---------
+
+.. code-block:: python
+
+   >>> resources = db.resources('file')
+
+Will return a generator object containing all file resources you're managing
+across your infrastructure. This is probably a bad idea if you have a big
+number of nodes as the response will be huge.
+
+Catalogs
+---------
+
+.. code-block:: python
+
+   >>> catalog = db.catalog('hostname')
+   >>> for res in catalog.get_resources():
+   >>>     print(res)
+
+Will return a Catalog object with the latest Catalog of the definded host. This
+catalog contains the defined Resources and Edges.
+
+Getting Help
+============
+This project is still very new so it's not inconceivable you'll run into
+issues.
+
+For bug reports you can file an `issue`_. If you need help with something
+feel free to hit up `@daenney`_ by e-mail or find him on IRC. He can usually
+be found on `IRCnet`_ and `Freenode`_ and idles in #puppet.
+
+There's now also the #puppetboard channel on `Freenode`_ where we hang out
+and answer questions related to pypuppetdb and Puppetboard.
+
+.. _issue: https://github.com/nedap/pypuppetdb/issues
+.. _ at daenney: https://github.com/daenney
+.. _IRCnet: http://www.ircnet.org
+.. _Freenode: http://freenode.net
+
+Documentation
+=============
+API documentation is automatically generated from the docstrings using
+Sphinx's autodoc feature. 
+
+Documentation will automatically be rebuilt on every push thanks to the
+Read The Docs webhook. You can `find it here`_.
+
+.. _find it here: https://pypuppetdb.readthedocs.org/en/latest/
+
+You can build the documentation manually by doing:
+
+.. code-block:: bash
+
+   $ cd docs
+   $ make html
+
+Doing so will only work if you have Sphinx installed, which you can acheive
+through:
+
+.. code-block:: bash
+
+   $ pip install -r requirements.txt
+
+Contributing
+============
+
+We welcome contributions to this library. However, there are a few ground
+rules contributors should be aware of.
+
+License
+-------
+This project is licensed under the Apache v2.0 License. As such, your
+contributions, once accepted, are automatically covered by this license.
+
+Commit messages
+---------------
+Write decent commit messages. Don't use swear words and refrain from
+uninformative commit messages as 'fixed typo'.
+
+The preferred format of a commit message:
+
+::
+
+    docs/quickstart: Fixed a typo in the Nodes section.
+
+    If needed, elaborate further on this commit. Feel free to write a
+    complete blog post here if that helps us understand what this is
+    all about.
+
+    Fixes #4 and resolves #2.
+
+If you'd like a more elaborate guide on how to write and format your commit
+messages have a look at this post by `Tim Pope`_.
+
+.. _Tim Pope: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+
+Tests
+-----
+Commits are expected to contain tests or updates to tests if they add to or
+modify the current behaviour.
+
+The test suite is powered by `pytest`_ and requires `pytest`_, `pytest-pep8`_,
+`httpretty`_ and `pytest-httpretty`_ which will be installed for you if you
+run:
+
+.. code-block:: bash
+
+   $ pip install -r requirements.txt
+
+.. _pytest: http://pytest.org/latest/
+.. _pytest-pep8: https://pypi.python.org/pypi/pytest-pep8
+.. _httpretty: https://pypi.python.org/pypi/httpretty/
+.. _pytest-httpretty: https://github.com/papaeye/pytest-httpretty
+
+To run the unit tests (the ones that don't require a live PuppetDB):
+
+.. code-block:: bash
+   
+   $ py.test -v -m unit
+
+If the tests pass, you're golden. If not we'll have to figure out why and
+fix that. Feel free to ask for help on this.
diff --git a/pypuppetdb.egg-info/PKG-INFO b/pypuppetdb.egg-info/PKG-INFO
new file mode 100644
index 0000000..4e78d5b
--- /dev/null
+++ b/pypuppetdb.egg-info/PKG-INFO
@@ -0,0 +1,378 @@
+Metadata-Version: 1.1
+Name: pypuppetdb
+Version: 0.1.1
+Summary: Library for working with the PuppetDB REST API.
+Home-page: https://github.com/nedap/pypuppetdb
+Author: Daniele Sluijters
+Author-email: daniele.sluijters+pypi at gmail.com
+License: Apache License 2.0
+Description: ##########
+        pypuppetdb
+        ##########
+        
+        .. image:: https://api.travis-ci.org/nedap/pypuppetdb.png
+           :target: https://travis-ci.org/nedap/pypuppetdb
+        
+        .. image:: https://coveralls.io/repos/nedap/pypuppetdb/badge.png
+           :target: https://coeralls.io/r/nedap/pypuppetdb
+        
+        .. image:: https://pypip.in/d/pypuppetdb/badge.png
+           :target: https://crate.io/packages/pypuppetdb
+        
+        .. image:: https://pypip.in/v/pypuppetdb/badge.png
+           :target: https://crate.io/packages/pypuppetdb
+        
+        pypuppetdtb is a library to work with PuppetDB's REST API. It is implemented
+        using the `requests`_ library.
+        
+        .. _requests: http://docs.python-requests.org/en/latest/
+        
+        This library is a thin wrapper around the REST API providing some convinience
+        functions and objects to request and hold data from PuppetDB.
+        
+        To use this library you will need:
+            * Python 2.6 or 2.7
+            * Python 3.3
+        
+        Installation
+        ============
+        
+        You can install this package from source or from PyPi.
+        
+        .. code-block:: bash
+        
+           $ pip install pypuppetdb
+        
+        .. code-block:: bash
+        
+           $ git clone https://github.com/nedap/pypuppetdb
+           $ python setup.py install
+        
+        If you wish to hack on it clone the repository but after that run:
+        
+        .. code-block:: bash
+        
+           $ pip install -r requirements.txt
+        
+        This will install all the runtime requirements of pypuppetdb and the
+        dependencies for the test suite and generation of documentation.
+        
+        Usage
+        =====
+        
+        Once you have pypuppetdb installed you can configure it to connect to PuppetDB
+        and take it from there.
+        
+        Connecting
+        ----------
+        
+        The first thing you need to do is to connect with PuppetDB:
+        
+        .. code-block:: python
+        
+           >>> from pypuppetdb import connect
+           >>> db = connect()
+        
+        Nodes
+        -----
+        
+        The following will return a generator object yielding Node objects for every
+        returned node from PuppetDB.
+        
+        .. code-block:: python
+        
+           >>> nodes = db.nodes()
+           >>> for node in nodes:
+           >>>   print(node)
+           host1
+           host2
+           ...
+        
+        To query a single node the singular `node()` can be used:
+        
+        .. code-block:: python
+        
+            >>> node = db.node('hostname')
+            >>> print(node)
+            hostname
+        
+        Node scope
+        ~~~~~~~~~~
+        
+        The Node objects are a bit more special in that they can query for facts and
+        resources themselves. Using those methods from a node object will automatically
+        add a query to the request scoping the request to the node.
+        
+        .. code-block:: python
+        
+           >>> node = db.node('hostname')
+           >>> print(node.fact('osfamily'))
+           osfamily/hostname
+        
+        Facts
+        -----
+        
+        .. code-block:: python
+        
+           >>> facts = db.facts('osfamily')
+           >>> for fact in facts:
+           >>>   print(fact)
+           osfamily/host1
+           osfamily/host2
+        
+        That queries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+        one per node this fact is known for.
+        
+        Resources
+        ---------
+        
+        .. code-block:: python
+        
+           >>> resources = db.resources('file')
+        
+        Will return a generator object containing all file resources you're managing
+        across your infrastructure. This is probably a bad idea if you have a big
+        number of nodes as the response will be huge.
+        
+        Catalogs
+        ---------
+        
+        .. code-block:: python
+        
+           >>> catalog = db.catalog('hostname')
+           >>> for res in catalog.get_resources():
+           >>>     print(res)
+        
+        Will return a Catalog object with the latest Catalog of the definded host. This
+        catalog contains the defined Resources and Edges.
+        
+        Getting Help
+        ============
+        This project is still very new so it's not inconceivable you'll run into
+        issues.
+        
+        For bug reports you can file an `issue`_. If you need help with something
+        feel free to hit up `@daenney`_ by e-mail or find him on IRC. He can usually
+        be found on `IRCnet`_ and `Freenode`_ and idles in #puppet.
+        
+        There's now also the #puppetboard channel on `Freenode`_ where we hang out
+        and answer questions related to pypuppetdb and Puppetboard.
+        
+        .. _issue: https://github.com/nedap/pypuppetdb/issues
+        .. _ at daenney: https://github.com/daenney
+        .. _IRCnet: http://www.ircnet.org
+        .. _Freenode: http://freenode.net
+        
+        Documentation
+        =============
+        API documentation is automatically generated from the docstrings using
+        Sphinx's autodoc feature. 
+        
+        Documentation will automatically be rebuilt on every push thanks to the
+        Read The Docs webhook. You can `find it here`_.
+        
+        .. _find it here: https://pypuppetdb.readthedocs.org/en/latest/
+        
+        You can build the documentation manually by doing:
+        
+        .. code-block:: bash
+        
+           $ cd docs
+           $ make html
+        
+        Doing so will only work if you have Sphinx installed, which you can acheive
+        through:
+        
+        .. code-block:: bash
+        
+           $ pip install -r requirements.txt
+        
+        Contributing
+        ============
+        
+        We welcome contributions to this library. However, there are a few ground
+        rules contributors should be aware of.
+        
+        License
+        -------
+        This project is licensed under the Apache v2.0 License. As such, your
+        contributions, once accepted, are automatically covered by this license.
+        
+        Commit messages
+        ---------------
+        Write decent commit messages. Don't use swear words and refrain from
+        uninformative commit messages as 'fixed typo'.
+        
+        The preferred format of a commit message:
+        
+        ::
+        
+            docs/quickstart: Fixed a typo in the Nodes section.
+        
+            If needed, elaborate further on this commit. Feel free to write a
+            complete blog post here if that helps us understand what this is
+            all about.
+        
+            Fixes #4 and resolves #2.
+        
+        If you'd like a more elaborate guide on how to write and format your commit
+        messages have a look at this post by `Tim Pope`_.
+        
+        .. _Tim Pope: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+        
+        Tests
+        -----
+        Commits are expected to contain tests or updates to tests if they add to or
+        modify the current behaviour.
+        
+        The test suite is powered by `pytest`_ and requires `pytest`_, `pytest-pep8`_,
+        `httpretty`_ and `pytest-httpretty`_ which will be installed for you if you
+        run:
+        
+        .. code-block:: bash
+        
+           $ pip install -r requirements.txt
+        
+        .. _pytest: http://pytest.org/latest/
+        .. _pytest-pep8: https://pypi.python.org/pypi/pytest-pep8
+        .. _httpretty: https://pypi.python.org/pypi/httpretty/
+        .. _pytest-httpretty: https://github.com/papaeye/pytest-httpretty
+        
+        To run the unit tests (the ones that don't require a live PuppetDB):
+        
+        .. code-block:: bash
+           
+           $ py.test -v -m unit
+        
+        If the tests pass, you're golden. If not we'll have to figure out why and
+        fix that. Feel free to ask for help on this.
+        
+        #########
+        Changelog
+        #########
+        
+        0.1.1
+        =====
+        
+        * Fix the license in our ``setup.py``. The license shouldn't be longer than
+          200 characters. We were including the full license tripping up tools like
+          bdist_rpm.
+        
+        0.1.0
+        =====
+        Significant changes have been made in this release. The complete v3 API is
+        now supported except for query pagination.
+        
+        Most changes are backwards compatible except for a change in the SSL
+        configuration. The previous behaviour was buggy and slightly misleading in
+        the names the options took:
+        
+        * ``ssl`` has been renamed to ``ssl_verify`` and now defaults to ``True``.
+        * Automatically use HTTPS if ``ssl_key`` and ``ssl_cert`` are provided.
+        
+        For additional instructions about getting SSL to work see the Quickstart
+        in the documentation.
+        
+        Deprecation
+        ------------
+        Support for API v2 will be dropped in the 0.2.x release series.
+        
+        New features
+        ------------
+        
+        The following features are **only** supported for **API v3**.
+        
+        The ``node()`` and ``nodes()`` function have gained the following options:
+        
+          * ``with_status=False``
+          * ``unreported=2``
+        
+        When ``with_status`` is set to ``True`` an additional query will be made using
+        the ``events-count`` endpoint scoped to the latest report. This will result in
+        an additional ``events`` and ``status`` keys on the node object. ``status``
+        will be either of ``changed``, ``unchanged`` or ``failed`` depending on if
+        ``events`` contains ``successes`` or ``failures`` or none.
+        
+        By default ``unreported`` is set to ``2``. This is only in effect when
+        ``with_status`` is set to ``True``. It means that if a node hasn't checked in
+        for two hours it will get a ``status`` of ``unreported`` instead.
+        
+        New endpoints:
+        
+          * ``events-count``: ``events_count()``
+          * ``aggregate-event-counts``: ``aggregate_event_counts()``
+          * ``server-time``: ``server_time()``
+          * ``version``: ``current_version()``
+          * ``catalog``: ``catalog()``
+        
+        New types:
+        
+          * ``pypuppetdb.types.Catalog``
+          * ``pypuppetdb.types.Edge``
+        
+        Changes to types:
+        
+          * ``pypuppetdb.types.Node`` now has:
+            * ``status`` defaulting to ``None``
+            * ``events`` defaulting to ``None``
+            * ``unreported_time`` defaulting to ``None``
+        
+        0.0.4
+        =====
+        
+        Due to a fairly serious bug 0.0.3 was pulled from PyPi minutes after release.
+        
+        When a bug was fixed to be able to query for all facts we accidentally
+        introduced a different bug that caused the ``facts()`` call on a node to
+        query for all facts because we were resetting the query.
+        
+        * Fix a bug where ``node.facts()`` was causing us to query all facts because
+          the query to scope our request was being reset.
+        
+        0.0.3
+        =====
+        
+        With the introduction of PuppetDB 1.5 a new API version, v3, was also
+        introduced. In that same release the old ``/experimental`` endpoints
+        were removed, meaning that as of PuppetDB 1.5 with the v2 API you can
+        no longer get access to reports or events.
+        
+        In light of this the support for the experimental endpoints has been
+        completely removed from pypuppetdb. As of this release you can only get
+        to reports and/or events through v3 of the API.
+        
+        This release includes preliminary support for the v3 API. Everything that
+        could be done with v2 plus the experimental endpoints is now possible on
+        v3. However, more advanced funtionality has not yet been implemented. That
+        will be the focus of the next release.
+        
+        * Removed dependency on pytz.
+        * Fixed the behaviour of ``facts()`` and ``resources()``. We can now
+          correctly query for all facts or resources.
+        * Fixed an issue with catalog timestampless nodes.
+        * Pass along the ``timeout`` option to ``connect()``.
+        * Added preliminary PuppetDB API v3 support.
+        * Removed support for the experimental endpoints.
+        * The ``connect()`` method defaults to API v3 now.
+        
+        0.0.2
+        =====
+        * Fix a bug in ``setup.py`` preventing successful installation.
+        
+        0.0.1
+        =====
+        Initial release. Implements most of the v2 API.
+        
+Keywords: puppet puppetdb
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Topic :: Software Development :: Libraries
diff --git a/pypuppetdb.egg-info/SOURCES.txt b/pypuppetdb.egg-info/SOURCES.txt
new file mode 100644
index 0000000..54737cd
--- /dev/null
+++ b/pypuppetdb.egg-info/SOURCES.txt
@@ -0,0 +1,19 @@
+CHANGELOG.rst
+LICENSE
+MANIFEST.in
+README.rst
+setup.cfg
+setup.py
+pypuppetdb/__init__.py
+pypuppetdb/errors.py
+pypuppetdb/package.py
+pypuppetdb/types.py
+pypuppetdb/utils.py
+pypuppetdb.egg-info/PKG-INFO
+pypuppetdb.egg-info/SOURCES.txt
+pypuppetdb.egg-info/dependency_links.txt
+pypuppetdb.egg-info/requires.txt
+pypuppetdb.egg-info/top_level.txt
+pypuppetdb/api/__init__.py
+pypuppetdb/api/v2.py
+pypuppetdb/api/v3.py
\ No newline at end of file
diff --git a/pypuppetdb.egg-info/dependency_links.txt b/pypuppetdb.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pypuppetdb.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/pypuppetdb.egg-info/requires.txt b/pypuppetdb.egg-info/requires.txt
new file mode 100644
index 0000000..ce40e87
--- /dev/null
+++ b/pypuppetdb.egg-info/requires.txt
@@ -0,0 +1 @@
+requests >= 1.2.3
\ No newline at end of file
diff --git a/pypuppetdb.egg-info/top_level.txt b/pypuppetdb.egg-info/top_level.txt
new file mode 100644
index 0000000..88c4c96
--- /dev/null
+++ b/pypuppetdb.egg-info/top_level.txt
@@ -0,0 +1 @@
+pypuppetdb
diff --git a/pypuppetdb/__init__.py b/pypuppetdb/__init__.py
new file mode 100644
index 0000000..0320911
--- /dev/null
+++ b/pypuppetdb/__init__.py
@@ -0,0 +1,114 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+"""
+pypuppetdb PuppetDB API library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+pypuppetdb is a library to work with PuppetDB's REST API. It provides a way
+to query PuppetDB and a set of additional methods and objects to make working
+with PuppetDB's API and the responses easier:
+
+    >>> from pypuppetdb import connect
+    >>> db = connect()
+    >>> nodes = db.nodes()
+    >>> print(nodes)
+    <generator object 'nodes'>
+    >>> for node in nodes:
+    >>>   print(node)
+    host1
+    host2
+    ...
+
+This will return a generator object yielding Node objects for every returned
+node from PuppetDB.
+
+To query a single node the singular db.node() can be used:
+
+   >>> node = db.node('hostname')
+   >>> print(node)
+   hostname
+
+The Node objects are a bit more special in that they can query for facts and
+resources themselves. Using those methods from a node object will automatically
+add a query to the request scoping the request to the node.
+
+   >>> node = db.node('hostname')
+   >>> print node.fact('osfamily')
+   osfamily/hostname
+
+We can also query for facts:
+
+   >>> facts = db.facts('osfamily')
+   >>> print(facts)
+   <generator object 'facts')
+   >>> for fact in facts:
+   >>> print(fact)
+   osfamily/host1
+   osfamily/host2
+
+That querries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+one per node this fact is found on.
+
+   >>> resources = db.resources('file')
+
+Will return a generator object containing all file resources you're managing
+across your infrastructure. This is probably a bad idea if you have a big
+number of nodes as the response will be huge.
+"""
+import logging
+
+from pypuppetdb.api import v2
+from pypuppetdb.api import v3
+from pypuppetdb.errors import UnsupportedVersionError
+
+try:  # Python 2.7+
+    from logging import NullHandler
+except ImportError:  # pragma: notest
+    class NullHandler(logging.Handler):
+        def emit(self, record):
+            pass
+
+logging.getLogger(__name__).addHandler(NullHandler())
+
+
+def connect(api_version=3, host='localhost', port=8080, ssl_verify=False,
+            ssl_key=None, ssl_cert=None, timeout=10):
+    """Connect with PuppetDB. This will return an object allowing you
+    to query the API through its methods.
+
+    :param api_version: Version of the API we're initialising.
+    :type api_version: :obj:`int`
+
+    :param host: (optional) Hostname or IP of PuppetDB.
+    :type host: :obj:`string`
+
+    :param port: (optional) Port on which to talk to PuppetDB.
+    :type port: :obj:`int`
+
+    :param ssl: (optional) Talk with PuppetDB over SSL.
+    :type ssl: :obj:`bool`
+
+    :param ssl_key: (optional) Path to our client secret key.
+    :type ssl_key: :obj:`None` or :obj:`string` representing a filesystem\
+            path.
+
+    :param ssl_cert: (optional) Path to our client certificate.
+    :type ssl_cert: :obj:`None` or :obj:`string` representing a filesystem\
+            path.
+
+    :param timeout: (optional) Number of seconds to wait for a response.
+    :type timeout: :obj:`int`
+
+    :raises: :class:`~pypuppetdb.errors.UnsupportedVersionError`
+    """
+    if api_version == 3:
+        return v3.API(host=host, port=port,
+                      timeout=timeout, ssl_verify=ssl_verify, ssl_key=ssl_key,
+                      ssl_cert=ssl_cert)
+    if api_version == 2:
+        return v2.API(host=host, port=port,
+                      timeout=timeout, ssl_verify=ssl_verify, ssl_key=ssl_key,
+                      ssl_cert=ssl_cert)
+    else:
+        raise UnsupportedVersionError
diff --git a/pypuppetdb/api/__init__.py b/pypuppetdb/api/__init__.py
new file mode 100644
index 0000000..a77178a
--- /dev/null
+++ b/pypuppetdb/api/__init__.py
@@ -0,0 +1,321 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+
+import json
+import requests
+
+from pypuppetdb.errors import (
+    ImproperlyConfiguredError,
+    EmptyResponseError,
+    UnsupportedVersionError,
+    APIError,
+    )
+
+log = logging.getLogger(__name__)
+
+API_VERSIONS = {
+    2: 'v2',
+    3: 'v3',
+}
+
+ENDPOINTS = {
+    2: {
+        'facts': 'facts',
+        'fact-names': 'fact-names',
+        'nodes': 'nodes',
+        'resources': 'resources',
+        'metrics': 'metrics',
+        'mbean': 'metrics/mbean',
+    },
+    3: {
+        'facts': 'facts',
+        'fact-names': 'fact-names',
+        'nodes': 'nodes',
+        'resources': 'resources',
+        'catalogs': 'catalogs',
+        'metrics': 'metrics',
+        'mbean': 'metrics/mbean',
+        'reports': 'reports',
+        'events': 'events',
+        'event-counts': 'event-counts',
+        'aggregate-event-counts': 'aggregate-event-counts',
+        'server-time': 'server-time',
+        'version': 'version',
+    },
+}
+
+ERROR_STRINGS = {
+    'timeout': 'Connection to PuppetDB timed out on',
+    'refused': 'Could not reach PuppetDB on',
+}
+
+
+class BaseAPI(object):
+    """This is a Base or Abstract class and is not meant to be instantiated
+    or used directly.
+
+    The BaseAPI object defines a set of methods that can be
+    reused across different versions of the PuppetDB API. If querying for a
+    certain resource is done in an identical fashion across different versions
+    it will be implemented here and should be overridden in their respective
+    versions if they deviate.
+
+    If :attr:`ssl` is set to `True` but either :attr:`ssl_key` or\
+            :attr:`ssl_cert` are `None` this will raise an error.
+
+    When at initialisation :obj:`api_version` isn't found in\
+            :const:`API_VERSIONS` this will raise an error.
+
+    :param api_version: Version of the API we're initialising.
+    :type api_version: :obj:`int`
+    :param host: (optional) Hostname or IP of PuppetDB.
+    :type host: :obj:`string`
+    :param port: (optional) Port on which to talk to PuppetDB.
+    :type port: :obj:`int`
+    :param ssl_verify: (optional) Verify PuppetDB server certificate.
+    :type ssl_verify: :obj:`bool`
+    :param ssl_key: (optional) Path to our client secret key.
+    :type ssl_key: :obj:`None` or :obj:`string` representing a filesystem\
+            path.
+    :param ssl_cert: (optional) Path to our client certificate.
+    :type ssl_cert: :obj:`None` or :obj:`string` representing a filesystem\
+            path.
+    :param timeout: (optional) Number of seconds to wait for a response.
+    :type timeout: :obj:`int`
+
+    :raises: :class:`~pypuppetdb.errors.ImproperlyConfiguredError`
+    :raises: :class:`~pypuppetdb.errors.UnsupportedVersionError`
+    """
+    def __init__(self, api_version, host='localhost', port=8080,
+                 ssl_verify=True, ssl_key=None, ssl_cert=None, timeout=10):
+        """Initialises our BaseAPI object passing the parameters needed in
+        order to be able to create the connection strings, set up SSL and
+        timeouts and so forth."""
+
+        if api_version in API_VERSIONS:
+            self.api_version = API_VERSIONS[api_version]
+        else:
+            raise UnsupportedVersionError
+
+        self.host = host
+        self.port = port
+        self.ssl_verify = ssl_verify
+        self.ssl_key = ssl_key
+        self.ssl_cert = ssl_cert
+        self.timeout = timeout
+        self.endpoints = ENDPOINTS[api_version]
+
+        if self.ssl_key is not None and self.ssl_cert is not None:
+            self.protocol = 'https'
+        else:
+            self.protocol = 'http'
+
+    @property
+    def version(self):
+        """The version of the API we're querying against.
+
+        :returns: Current API version.
+        :rtype: :obj:`string`"""
+        return self.api_version
+
+    @property
+    def base_url(self):
+        """A base_url that will be used to construct the final
+        URL we're going to query against.
+
+        :returns: A URL of the form: ``proto://host:port``.
+        :rtype: :obj:`string`
+        """
+        return '{proto}://{host}:{port}'.format(
+            proto=self.protocol,
+            host=self.host,
+            port=self.port,
+            )
+
+    @property
+    def total(self):
+        """The total-count of the last request to PuppetDB
+        if enabled as parameter in _query method
+
+        :returns Number of total results
+        :rtype :obj:`int`
+        """
+        if self.last_total is not None:
+            return int(self.last_total)
+
+    def _url(self, endpoint, path=None):
+        """The complete URL we will end up querying. Depending on the
+        endpoint we pass in  this will result in different URL's with
+        different prefixes.
+
+        :param endpoint: The PuppetDB API endpoint we want to query.
+        :type endpoint: :obj:`string`
+        :param path: An additional path if we don't wish to query the\
+                bare endpoint.
+        :type path: :obj:`string`
+
+        :returns: A URL constructed from :func:`base_url` with the\
+                apropraite API version/prefix and the rest of the path added\
+                to it.
+        :rtype: :obj:`string`
+        """
+
+        log.debug('_url called with endpoint: {0} and path: {1}'.format(
+            endpoint, path))
+
+        if endpoint in self.endpoints:
+            api_prefix = self.api_version
+            endpoint = self.endpoints[endpoint]
+        else:
+            # If we reach this we're trying to query an endpoint that doesn't
+            # exist. This shouldn't happen unless someone made a booboo.
+            raise APIError
+
+        url = '{base_url}/{api_prefix}/{endpoint}'.format(
+            base_url=self.base_url,
+            api_prefix=api_prefix,
+            endpoint=endpoint,
+            )
+
+        if path is not None:
+            url = '{0}/{1}'.format(url, path)
+
+        return url
+
+    def _query(self, endpoint, path=None, query=None,
+               order_by=None, limit=None, offset=None, include_total=False,
+               summarize_by=None, count_by=None, count_filter=None):
+        """This method actually querries PuppetDB. Provided an endpoint and an
+        optional path and/or query it will fire a request at PuppetDB. If
+        PuppetDB can be reached and answers within the timeout we'll decode
+        the response and give it back or raise for the HTTP Status Code
+        PuppetDB gave back.
+
+        :param endpoint: The PuppetDB API endpoint we want to query.
+        :type endpoint: :obj:`string`
+        :param path: An additional path if we don't wish to query the\
+                bare endpoint.
+        :type path: :obj:`string`
+        :param query: (optional) A query to further narrow down the resultset.
+        :type query: :obj:`string`
+        :param order_by: (optional) Set the order parameters for the resultset.
+        :type order_by: :obj:`string`
+        :param limit: (optional) Tell PuppetDB to limit it's response to this\
+                number of objects.
+        :type limit: :obj:`int`
+        :param offset: (optional) Tell PuppetDB to start it's response from\
+                the given offset. This is useful for implementing pagination\
+                but is not supported just yet.
+        :type offset: :obj:`string`
+        :param include_total: (optional) Include the total number of results
+        :type order_by: :obj:`bool`
+        :param summarize_by: (optional) Specify what type of object you'd like\
+                to see counts at the event-counts and aggregate-event-counts \
+                endpoints
+        :type summarize_by: :obj:`string`
+        :param count_by: (optional) Specify what type of object is counted
+        :type count_by: :obj:`string`
+        :param count_filter: (optional) Specify a filter for the results
+        :type count_filter: :obj:`string`
+
+        :raises: :class:`~pypuppetdb.errors.EmptyResponseError`
+
+        :returns: The decoded response from PuppetDB
+        :rtype: :obj:`dict` or :obj:`list`
+        """
+        log.debug('_query called with endpoint: {0}, path: {1}, query: {2}, '
+                  'limit: {3}, offset: {4}, summarize_by {5}, count_by {6}, '
+                  'count_filter: {7}'.format(endpoint, path, query, limit,
+                                             offset, summarize_by, count_by,
+                                             count_filter))
+
+        url = self._url(endpoint, path=path)
+        headers = {
+            'content-type': 'application/json',
+            'accept': 'application/json',
+            'accept-charset': 'utf-8'
+            }
+
+        payload = {}
+        if query is not None:
+            payload['query'] = query
+        if order_by is not None:
+            payload['order-by'] = order_by
+        if limit is not None:
+            payload['limit'] = limit
+        if include_total is True:
+            payload['include-total'] = json.dumps(include_total)
+        if offset is not None:
+            payload['offset'] = offset
+        if summarize_by is not None:
+            payload['summarize-by'] = summarize_by
+        if count_by is not None:
+            payload['count-by'] = count_by
+        if count_filter is not None:
+            payload['count-filter'] = count_filter
+
+        if not (payload):
+            payload = None
+
+        try:
+            r = requests.get(url, params=payload, headers=headers,
+                             verify=self.ssl_verify, cert=(self.ssl_cert,
+                                                           self.ssl_key),
+                             timeout=self.timeout)
+            r.raise_for_status()
+
+            # get total number of results if requested with include-total
+            # just a quick hack - needs improvement
+            if 'X-Records' in r.headers:
+                self.last_total = r.headers['X-Records']
+            else:
+                self.last_total = None
+
+            json_body = r.json()
+            if json_body is not None:
+                return json_body
+            else:
+                del json_body
+                raise EmptyResponseError
+
+        except requests.exceptions.Timeout:
+            log.error("{0} {1}:{2} over {3}.".format(ERROR_STRINGS['timeout'],
+                                                     self.host, self.port,
+                                                     self.protocol.upper()))
+            raise
+        except requests.exceptions.ConnectionError:
+            log.error("{0} {1}:{2} over {3}.".format(ERROR_STRINGS['refused'],
+                                                     self.host, self.port,
+                                                     self.protocol.upper()))
+            raise
+        except requests.exceptions.HTTPError as err:
+            log.error("{0} {1}:{2} over {3}.".format(err.response.text,
+                                                     self.host, self.port,
+                                                     self.protocol.upper()))
+            raise
+
+    # Method stubs
+
+    def nodes(self):
+        raise NotImplementedError
+
+    def node(self):
+        raise NotImplementedError
+
+    def facts(self):
+        raise NotImplementedError
+
+    def resources(self):
+        raise NotImplementedError
+
+    def metric(self, metric):
+        """Query for a specific metrc.
+
+        :param metric: The name of the metric we want.
+        :type metric: :obj:`string`
+
+        :returns: The return of :meth:`~pypuppetdb.api.BaseAPI._query`.
+        """
+        return self._query('mbean', path=metric)
diff --git a/pypuppetdb/api/v2.py b/pypuppetdb/api/v2.py
new file mode 100644
index 0000000..185c23a
--- /dev/null
+++ b/pypuppetdb/api/v2.py
@@ -0,0 +1,122 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+
+from pypuppetdb.api import BaseAPI
+from pypuppetdb.types import (
+    Node, Fact, Resource,
+    )
+
+log = logging.getLogger(__name__)
+
+
+class API(BaseAPI):
+    """The API object for version 2 of the PuppetDB API. This object contains
+    all v2 specific methods and ways of doing things.
+
+    :param \*\*kwargs: Rest of the keywoard arguments passed on to our parent\
+            :class:`~pypuppetdb.api.BaseAPI`.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Initialise the API object."""
+        super(API, self).__init__(api_version=2, **kwargs)
+        log.debug('API initialised with {0}'.format(kwargs))
+
+    def node(self, name):
+        """Gets a single node from PuppetDB."""
+        nodes = self.nodes(name=name)
+        return next(node for node in nodes)
+
+    def nodes(self, name=None, query=None):
+        """Query for nodes by either name or query. If both aren't
+        provided this will return a list of all nodes.
+
+        :param name: (optional)
+        :type name: :obj:`None` or :obj:`string`
+        :param query: (optional)
+        :type query: :obj:`None` or :obj:`string`
+
+        :returns: A generator yieling Nodes.
+        :rtype: :class:`pypuppetdb.types.Node`
+        """
+
+        nodes = self._query('nodes', path=name, query=query)
+        # If we happen to only get one node back it
+        # won't be inside a list so iterating over it
+        # goes boom. Therefor we wrap a list around it.
+        if type(nodes) == dict:
+            log.debug("Request returned a single node.")
+            nodes = [nodes, ]
+
+        for node in nodes:
+            yield Node(self,
+                       node['name'],
+                       deactivated=node['deactivated'],
+                       report_timestamp=node['report_timestamp'],
+                       catalog_timestamp=node['catalog_timestamp'],
+                       facts_timestamp=node['facts_timestamp'],
+                       )
+
+    def facts(self, name=None, value=None, query=None):
+        """Query for facts limited by either name, value and/or query.
+        This will yield a single Fact object at a time."""
+
+        log.debug('{0}, {1}, {2}'.format(name, value, query))
+        if name is not None and value is not None:
+            path = '{0}/{1}'.format(name, value)
+        elif name is not None and value is None:
+            path = name
+        elif name is None and value is None and query is not None:
+            path = None
+        else:
+            log.debug("We want to query for all facts.")
+            query = ''
+            path = None
+
+        facts = self._query('facts', path=path, query=query)
+        for fact in facts:
+            yield Fact(
+                fact['certname'],
+                fact['name'],
+                fact['value'],
+                )
+
+    def fact_names(self):
+        """Get a list of all known facts."""
+
+        return self._query('fact-names')
+
+    def resources(self, type_=None, title=None, query=None):
+        """Query for resources limited by either type and/or title or query.
+        This will yield a Resources object for every returned resource."""
+
+        if type_ is not None:
+            # Need to capitalize the resource type since PuppetDB doesn't
+            # answer to lower case type names.
+            # bugs.puppetlabs.com/some_value
+            type_ = type_.capitalize()
+            if title is not None:
+                path = '{0}/{1}'.format(type_, title)
+            elif title is None:
+                path = type_
+        else:
+            log.debug('Going to query for all resources. This is usually a '
+                      'bad idea as it might return enormous amounts of '
+                      'resources.')
+            query = ''
+            path = None
+
+        resources = self._query('resources', path=path, query=query)
+        for resource in resources:
+            yield Resource(
+                resource['certname'],
+                resource['title'],
+                resource['type'],
+                resource['tags'],
+                resource['exported'],
+                resource['sourcefile'],
+                resource['sourceline'],
+                resource['parameters'],
+                )
diff --git a/pypuppetdb/api/v3.py b/pypuppetdb/api/v3.py
new file mode 100644
index 0000000..f5a8705
--- /dev/null
+++ b/pypuppetdb/api/v3.py
@@ -0,0 +1,254 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+
+from pypuppetdb.api import BaseAPI
+from pypuppetdb.utils import json_to_datetime
+from datetime import datetime, timedelta
+from pypuppetdb.types import (
+    Node, Fact, Resource,
+    Report, Event, Catalog
+    )
+
+log = logging.getLogger(__name__)
+
+
+class API(BaseAPI):
+    """The API object for version 3 of the PuppetDB API. This object contains
+    all v3 specific methods and ways of doing things.
+
+    :param \*\*kwargs: Rest of the keywoard arguments passed on to our parent\
+            :class:`~pypuppetdb.api.BaseAPI`.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Initialise the API object."""
+        super(API, self).__init__(api_version=3, **kwargs)
+        log.debug('API initialised with {0}.'.format(kwargs))
+
+    def node(self, name):
+        """Gets a single node from PuppetDB."""
+        nodes = self.nodes(name=name)
+        return next(node for node in nodes)
+
+    def nodes(self, name=None, query=None, unreported=2, with_status=False):
+        """Query for nodes by either name or query. If both aren't
+        provided this will return a list of all nodes. This method
+        also fetches the nodes status and event counts of the latest
+        report from puppetdb.
+
+        :param name: (optional)
+        :type name: :obj:`None` or :obj:`string`
+        :param query: (optional)
+        :type query: :obj:`None` or :obj:`string`
+        :param with_status: (optional) include the node status in the\
+                           returned nodes
+        :type with_status: :bool:
+        :param unreported: (optional) amount of hours when a node gets
+                           marked as unreported
+        :type unreported: :obj:`None` or integer
+
+        :returns: A generator yieling Nodes.
+        :rtype: :class:`pypuppetdb.types.Node`
+        """
+        nodes = self._query('nodes', path=name, query=query)
+        # If we happen to only get one node back it
+        # won't be inside a list so iterating over it
+        # goes boom. Therefor we wrap a list around it.
+        if type(nodes) == dict:
+            nodes = [nodes, ]
+
+        if with_status:
+            latest_events = self._query(
+                'event-counts',
+                query='["=","latest-report?",true]',
+                summarize_by='certname')
+
+        for node in nodes:
+            node['unreported_time'] = None
+            node['status'] = None
+
+            if with_status:
+                status = [s for s in latest_events
+                          if s['subject']['title'] == node['name']]
+
+            # node status from events
+            if with_status and status:
+                node['events'] = status = status[0]
+                if status['successes'] > 0:
+                    node['status'] = 'changed'
+                if status['failures'] > 0:
+                    node['status'] = 'failed'
+            else:
+                if with_status:
+                    node['status'] = 'unchanged'
+                node['events'] = None
+
+            # node report age
+            if with_status and node['report_timestamp'] is not None:
+                try:
+                    last_report = json_to_datetime(node['report_timestamp'])
+                    last_report = last_report.replace(tzinfo=None)
+                    now = datetime.utcnow()
+                    unreported_border = now-timedelta(hours=unreported)
+                    if last_report < unreported_border:
+                        delta = (datetime.utcnow()-last_report)
+                        node['status'] = 'unreported'
+                        node['unreported_time'] = '{0}d {1}h {2}m'.format(
+                            delta.days,
+                            int(delta.seconds/3600),
+                            int((delta.seconds % 3600)/60)
+                            )
+                except AttributeError:
+                    node['status'] = 'unreported'
+
+            if not node['report_timestamp'] and with_status:
+                node['status'] = 'unreported'
+
+            yield Node(self,
+                       node['name'],
+                       deactivated=node['deactivated'],
+                       report_timestamp=node['report_timestamp'],
+                       catalog_timestamp=node['catalog_timestamp'],
+                       facts_timestamp=node['facts_timestamp'],
+                       status=node['status'],
+                       events=node['events'],
+                       unreported_time=node['unreported_time']
+                       )
+
+    def facts(self, name=None, value=None, query=None):
+        """Query for facts limited by either name, value and/or query.
+        This will yield a single Fact object at a time."""
+
+        log.debug('{0}, {1}, {2}'.format(name, value, query))
+        if name is not None and value is not None:
+            path = '{0}/{1}'.format(name, value)
+        elif name is not None and value is None:
+            path = name
+        elif name is None and value is None and query is not None:
+            path = None
+        else:
+            log.debug("We want to query for all facts.")
+            query = ''
+            path = None
+
+        facts = self._query('facts', path=path, query=query)
+        for fact in facts:
+            yield Fact(
+                fact['certname'],
+                fact['name'],
+                fact['value'],
+                )
+
+    def fact_names(self):
+        """Get a list of all known facts."""
+
+        return self._query('fact-names')
+
+    def resources(self, type_=None, title=None, query=None):
+        """Query for resources limited by either type and/or title or query.
+        This will yield a Resources object for every returned resource."""
+
+        log.debug('YOLO')
+        if type_ is not None:
+            # Need to capitalize the resource type since PuppetDB doesn't
+            # answer to lower case type names.
+            # bugs.puppetlabs.com/some_value
+            type_ = type_.capitalize()
+            if title is not None:
+                path = '{0}/{1}'.format(type_, title)
+            elif title is None:
+                path = type_
+        else:
+            log.debug('Going to query for all resources. This is usually a '
+                      'bad idea as it might return enormous amounts of '
+                      'resources.')
+            query = ''
+            path = None
+
+        resources = self._query('resources', path=path, query=query)
+        for resource in resources:
+            yield Resource(
+                resource['certname'],
+                resource['title'],
+                resource['type'],
+                resource['tags'],
+                resource['exported'],
+                resource['file'],
+                resource['line'],
+                resource['parameters'],
+                )
+
+    def reports(self, query):
+        """Get reports for our infrastructure. Currently reports can only
+        be filtered through a query which requests a specific certname.
+        If not it will return all reports.
+
+        This yields a Report object for every returned report."""
+        reports = self._query('reports', query=query)
+        for report in reports:
+            yield Report(
+                report['certname'],
+                report['hash'],
+                report['start-time'],
+                report['end-time'],
+                report['receive-time'],
+                report['configuration-version'],
+                report['report-format'],
+                report['puppet-version'],
+                report['transaction-uuid']
+                )
+
+    def events(self, query):
+        """A report is made up of events. This allows to query for events
+        based on the reprt hash.
+        This yields an Event object for every returned event."""
+
+        events = self._query('events', query=query)
+        for event in events:
+            yield Event(
+                event['certname'],
+                event['status'],
+                event['timestamp'],
+                event['report'],
+                event['resource-title'],
+                event['property'],
+                event['message'],
+                event['new-value'],
+                event['old-value'],
+                event['resource-type'],
+                )
+
+    def event_counts(self, query, summarize_by,
+                     count_by=None, count_filter=None):
+        """Get event counts from puppetdb"""
+        return self._query('event-counts',
+                           query=query,
+                           summarize_by=summarize_by,
+                           count_by=count_by,
+                           count_filter=count_filter)
+
+    def aggregate_event_counts(self, query, summarize_by,
+                               count_by=None, count_filter=None):
+        """Get event counts from puppetdb"""
+        return self._query('aggregate-event-counts',
+                           query=query, summarize_by=summarize_by,
+                           count_by=count_by, count_filter=count_filter)
+
+    def server_time(self):
+        """Get the current time of the clock on the PuppetDB server"""
+        return self._query('server-time')['server-time']
+
+    def current_version(self):
+        """Get version information about the running PuppetDB server"""
+        return self._query('version')['version']
+
+    def catalog(self, node):
+        """Get the most recent catalog for a given node"""
+        c = self._query('catalogs', path=node)
+        return Catalog(c['data']['name'],
+                       c['data']['edges'],
+                       c['data']['resources'],
+                       c['data']['version'],
+                       c['data']['transaction-uuid'])
diff --git a/pypuppetdb/errors.py b/pypuppetdb/errors.py
new file mode 100644
index 0000000..4e7780d
--- /dev/null
+++ b/pypuppetdb/errors.py
@@ -0,0 +1,30 @@
+class APIError(Exception):
+    """Our base exception the other errors inherit from."""
+    pass
+
+
+class ImproperlyConfiguredError(APIError):
+    """This exception is thrown when the API is initialised
+    and it detects incompatbile configuration such as SSL turned
+    on but no certificates provided."""
+    pass
+
+
+class EmptyResponseError(APIError):
+    """Will be thrown when we did recieve a response but the
+    response is empty."""
+    pass
+
+
+class UnsupportedVersionError(APIError):
+    """Triggers when using the :func:`connect` function and
+    providing it with an unknown API version."""
+    pass
+
+
+class DoesNotComputeError(APIError):
+    """This error will be thrown when a function is called with
+    an incompatible set of optional parameters. This is the 'you are
+    being a naughty developer, go read the docs' error.
+    """
+    pass
diff --git a/pypuppetdb/package.py b/pypuppetdb/package.py
new file mode 100644
index 0000000..2a98591
--- /dev/null
+++ b/pypuppetdb/package.py
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+
+__title__ = 'pypuppetdb'
+__version_info__ = (0, 1, 1)  # notest
+__version__ = '.'.join("{0}".format(num) for num in __version_info__)  # notest
+__author__ = 'Daniele Sluijters'
+__license__ = 'Apache License 2.0'
+__year__ = '2013, 2014'
+__copyright__ = 'Copyright {0} {1}'.format(__year__, __author__)
diff --git a/pypuppetdb/types.py b/pypuppetdb/types.py
new file mode 100644
index 0000000..1a1af94
--- /dev/null
+++ b/pypuppetdb/types.py
@@ -0,0 +1,408 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+from pypuppetdb.utils import json_to_datetime
+
+log = logging.getLogger(__name__)
+
+
+class Event(object):
+    """This object represents an event.
+
+    :param node: The hostname of the node this event fired on.
+    :param status: The status for the event.
+    :param timestamp: A timestamp of when this event occured.
+    :param hash\_: The hash of this event.
+    :param title: The resource title this event was fired for.
+    :param property\_: The property of the resource this event was fired for.
+    :param message: A message associated with this event.
+    :param new_value: The new value/state of the resource.
+    :param old_value: The old value/state of the resource.
+    :param type\_: The type of the resource this event fired for.
+
+    :ivar status: A :obj:`string` of this event's status.
+    :ivar failed: The :obj:`bool` equivalent of `status`.
+    :ivar timestamp: A :obj:`datetime.datetime` of when this event happend.
+    :ivar node: The hostname of the machine this event\
+            occured on.
+    :ivar hash\_: The hash of this event.
+    :ivar item: :obj:`dict` with information about the item/resource this\
+            event was triggered for.
+    """
+    def __init__(self, node, status, timestamp, hash_, title, property_,
+                 message, new_value, old_value, type_):
+        self.node = node
+        self.status = status
+        if self.status == 'failure':
+            self.failed = True
+        else:
+            self.failed = False
+        self.timestamp = json_to_datetime(timestamp)
+        self.hash_ = hash_
+        self.item = {}
+        self.item['title'] = title
+        self.item['type'] = type_
+        self.item['property'] = property_
+        self.item['message'] = message
+        self.item['old'] = old_value
+        self.item['new'] = new_value
+        self.__string = '{0}[{1}]/{2}'.format(self.item['type'],
+                                              self.item['title'],
+                                              self.hash_)
+
+    def __repr__(self):
+        return str('Event: {0}'.format(self.__string))
+
+    def __str__(self):
+        return str('{0}').format(self.__string)
+
+    def __unicode__(self):
+        return self.__string
+
+
+class Report(object):
+    """This object represents a report.
+
+    :param node: The hostname of the node this report originated on.
+    :param hash\_: A string uniquely identifying this report.
+    :param start: The start time of the agent run.
+    :type start: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+    :param end: The time the agent finished its run.
+    :type end: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+    :param received: The time PuppetDB received the report.
+    :type received: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+    :param version: The catalog / configuration version.
+    :type version: :obj:`string`
+    :param format\_: The catalog format version.
+    :type format\_: :obj:`int`
+    :param agent_version: The Puppet agent version.
+    :type agent_version: :obj:`string`
+    :param transaction: The UUID of this transaction.
+    :type transaction: :obj:`string`
+
+    :ivar node: The hostname this report originated from.
+    :ivar hash\_: Unique identifier of this report.
+    :ivar start: :obj:`datetime.datetime` when the Puppet agent run started.
+    :ivar end: :obj:`datetime.datetime` when the Puppet agent run ended.
+    :ivar received: :obj:`datetime.datetime` when the report finished\
+            uploading.
+    :ivar version: :obj:`string` catalog configuration version.
+    :ivar format\_: :obj:`int` catalog format version.
+    :ivar agent_version: :obj:`string` Puppet Agent version.
+    :ivar run_time: :obj:`datetime.timedelta` of **end** - **start**.
+    :ivar transaction: UUID identifying this transaction.
+
+    """
+    def __init__(self, node, hash_, start, end, received, version,
+                 format_, agent_version, transaction):
+
+        self.node = node
+        self.hash_ = hash_
+        self.start = json_to_datetime(start)
+        self.end = json_to_datetime(end)
+        self.received = json_to_datetime(received)
+        self.version = version
+        self.format_ = format_
+        self.agent_version = agent_version
+        self.run_time = self.end - self.start
+        self.transaction = transaction
+        self.__string = '{0}'.format(self.hash_)
+
+    def __repr__(self):
+        return str('Report: {0}'.format(self.__string))
+
+    def __str__(self):
+        return str('{0}').format(self.__string)
+
+    def __unicode__(self):
+        return self.__string
+
+
+class Fact(object):
+    """his object represents a fact.
+
+    :param node: The hostname this fact was collected from.
+    :param name: The fact's name, such as 'osfamily'
+    :param value: The fact's value, such as 'Debian'
+
+    :ivar node: :obj:`string` holding the hostname.
+    :ivar name: :obj:`string` holding the fact's name.
+    :ivar value: :obj:`string` holding the fact's value.
+    """
+    def __init__(self, node, name, value):
+        self.node = node
+        self.name = name
+        self.value = value
+        self.__string = '{0}/{1}'.format(self.name, self.node)
+
+    def __repr__(self):
+        return str('Fact: {0}'.format(self.__string))
+
+    def __str__(self):
+        return str('{0}').format(self.__string)
+
+    def __unicode__(self):
+        return self.__string
+
+
+class Resource(object):
+    """This object represents a resource.
+
+    :param node: The hostname this resource is located on.
+    :param name: The name of the resource in the Puppet manifest.
+    :param type\_: Type of the Puppet resource.
+    :param tags: Tags associated with this resource.
+    :type tags: :obj:`list`
+    :param exported: If it's an exported resource.
+    :type exported: :obj:`bool`
+    :param sourcefile: The Puppet manifest this resource is declared in.
+    :param sourceline: The line this resource is declared at.
+    :param parameters: The parameters this resource has been declared with.
+    :type parameters: :obj:`dict`
+
+    :ivar node: The hostname this resources is located on.
+    :ivar name: The name of the resource in the Puppet manifest.
+    :ivar type\_: The type of Puppet resource.
+    :ivar exported: :obj:`bool` if the resource is exported.
+    :ivar sourcefile: The Puppet manifest this resource is declared in.
+    :ivar sourceline: The line this resource is declared at.
+    :ivar parameters: :obj:`dict` with key:value pairs of parameters.
+    """
+    def __init__(self, node, name, type_, tags, exported, sourcefile,
+                 sourceline, parameters={}):
+        self.node = node
+        self.name = name
+        self.type_ = type_
+        self.tags = tags
+        self.exported = exported
+        self.sourcefile = sourcefile
+        self.sourceline = sourceline
+        self.parameters = parameters
+        self.__string = '{0}[{1}]'.format(self.type_, self.name)
+
+    def __repr__(self):
+        return str('<Resource: {0}>').format(self.__string)
+
+    def __str__(self):
+        return str('{0}').format(self.__string)
+
+    def __unicode__(self):
+        return self.__string
+
+
+class Node(object):
+    """This object represents a node. It additionally has some helper methods
+    so that you can query for resources or facts directly from the node scope.
+
+    :param api: API object.
+    :param name: Hostname of this node.
+    :param deactivated: (default `None`) Time this node was deactivated at.
+    :type deactivated: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+    :param report_timestamp: (default `None`) Time of the last report.
+    :type report_timestamp: :obj:`string` formatted as\
+            ``%Y-%m-%dT%H:%M:%S.%fZ``
+    :param catalog_timestamp: (default `None`) Time the last time a catalog\
+            was compiled.
+    :type catalog_timestamp: :obj:`string` formatted as\
+            ``%Y-%m-%dT%H:%M:%S.%fZ``
+    :param facts_timestamp: (default `None`) Time the last time facts were\
+            collected.
+    :type facts_timestamp: :obj:`string` formatted as\
+            ``%Y-%m-%dT%H:%M:%S.%fZ``
+    :param status: (default `None`) Status of the node\
+            changed | unchanged | unreported | failed
+    :type status: :obj:`string`
+    :param events: (default `None`) Counted events from latest Report
+    :type events: :obj:`dict`
+    :param unreported_time: (default `None`) Time since last report
+    :type unreported_time: :obj:`string`
+
+    :ivar name: Hostname of this node.
+    :ivar deactivated: :obj:`datetime.datetime` when this host was\
+            deactivated or `False`.
+    :ivar report_timestamp: :obj:`datetime.datetime` when the last run\
+            occured or `None`.
+    :ivar catalog_timestamp: :obj:`datetime.datetime` last time a catalog was\
+            compiled or `None`.
+    :ivar facts_timestamp: :obj:`datetime.datetime` last time when facts were\
+            collected or `None`.
+    """
+    def __init__(self, api, name, deactivated=None, report_timestamp=None,
+                 catalog_timestamp=None, facts_timestamp=None,
+                 status=None, events=None, unreported_time=None):
+        self.name = name
+        self.status = status
+        self.events = events
+        self.unreported_time = unreported_time
+
+        if deactivated is not None:
+            self.deactivated = json_to_datetime(deactivated)
+        else:
+            self.deactivated = False
+        if report_timestamp is not None:
+            self.report_timestamp = json_to_datetime(report_timestamp)
+        else:
+            self.report_timestamp = report_timestamp
+        if facts_timestamp is not None:
+            self.facts_timestamp = json_to_datetime(facts_timestamp)
+        else:
+            self.facts_timestamp = facts_timestamp
+        if catalog_timestamp is not None:
+            self.catalog_timestamp = json_to_datetime(catalog_timestamp)
+        else:
+            self.catalog_timestamp = catalog_timestamp
+
+        self.__api = api
+        self.__query_scope = '["=", "certname", "{0}"]'.format(self.name)
+        self.__string = self.name
+
+    def __repr__(self):
+        return str('<Node: {0}>').format(self.__string)
+
+    def __str__(self):
+        return str('{0}').format(self.__string)
+
+    def __unicode__(self):
+        return self.__string
+
+    def facts(self):
+        """Get all facts of this node."""
+        return self.__api.facts(query=self.__query_scope)
+
+    def fact(self, name):
+        """Get a single fact from this node."""
+        facts = self.__api.facts(name=name, query=self.__query_scope)
+        return next(fact for fact in facts)
+
+    def resources(self, type_=None, title=None):
+        """Get all resources of this node or all resources of the specified
+        type."""
+        if type_ is None:
+            resources = self.__api.resources(query=self.__query_scope)
+        elif type_ is not None and title is None:
+            resources = self.__api.resources(type_=type_,
+                                             query=self.__query_scope)
+        else:
+            resources = self.__api.resources(type_=type_, title=title,
+                                             query=self.__query_scope)
+        return resources
+
+    def resource(self, type_, title):
+        """Get a resource matching the supplied type and title."""
+        resources = self.__api.resources(type_=type_, title=title,
+                                         query=self.__query_scope)
+        return next(resource for resource in resources)
+
+    def reports(self):
+        """Get all reports for this node."""
+        return self.__api.reports(self.__query_scope)
+
+
+class Catalog(object):
+    """
+    This object represents a compiled catalog from puppet. It contains Resource
+    and Edge object that represent the dependency graph.
+
+    :param node: Name of the host
+    :type edges: :obj:`string`
+    :param edges: Edges returned from Catalog data
+    :type edges: :obj:`list` containing :obj:`dict` with Edge information
+    :param resources: Resources returned from Catalog data
+    :type resources: :obj:`list` containing :obj:`dict` with Resources
+    :param version: Catalog version from Puppet (unique for each node)
+    :type version: :obj:`string`
+    :param transaction_uuid: A string used to match the catalog with the
+                             corresponding report that was issued during
+                             the same puppet run
+    :type transaction_uuid: :obj:`string`
+
+    :ivar node: :obj:`string` Name of the host
+    :ivar version: :obj:`string` Catalog version from Puppet
+                                 (unique for each node)
+    :ivar transaction_uuid: :obj:`string` used to match the catalog with
+                                          corresponding report
+    :ivar edges: :obj:`list` of :obj:`Edge` The source Resource object\
+                 of the relationship
+    :ivar resources: :obj:`dict` of :obj:`Resource` The source Resource\
+                     object of the relationship
+    """
+    def __init__(self, node, edges, resources,
+                 version, transaction_uuid):
+
+        self.node = node
+        self.version = version
+        self.transaction_uuid = transaction_uuid
+
+        self.resources = dict()
+        for resource in resources:
+            if 'file' not in resource:
+                resource['file'] = None
+            if 'line' not in resource:
+                resource['line'] = None
+            identifier = resource['type']+'['+resource['title']+']'
+            res = Resource(node, resource['title'],
+                           resource['type'], resource['tags'],
+                           resource['exported'], resource['file'],
+                           resource['line'], resource['parameters'])
+            self.resources[identifier] = res
+
+        self.edges = []
+        for edge in edges:
+            identifier_source = edge['source']['type'] + \
+                '[' + edge['source']['title'] + ']'
+            identifier_target = edge['target']['type'] + \
+                '[' + edge['target']['title'] + ']'
+            self.edges.append(Edge(self.resources[identifier_source],
+                              self.resources[identifier_target],
+                              edge['relationship']))
+
+        self.__string = '{0}/{1}'.format(self.node, self.transaction_uuid)
+
+    def __repr__(self):
+        return str('<Catalog: {0}>').format(self.__string)
+
+    def __str__(self):
+        return str('{0}').format(self.__string)
+
+    def __unicode__(self):
+        return self.__string
+
+    def get_resources(self):
+        return self.resources.itervalues()
+
+    def get_edges(self):
+        return iter(self.edges)
+
+
+class Edge(object):
+    """
+    This object represents the connection between two Resource objects
+
+    :param source: The source Resource object of the relationship
+    :type source: :obj:`Resource`
+    :param target: The target Resource object of the relationship
+    :type target: :obj:`Resource`
+    :param relaptionship: Name of the Puppet Ressource Relationship
+    :type relationship: :obj:`string`
+
+    :ivar source: :obj:`Resource` The source Resource object
+    :ivar target: :obj:`Resource` The target Resource object
+    :ivar relationship: :obj:`string` Name of the Puppet Resource relationship
+    """
+    def __init__(self, source, target, relationship):
+        self.source = source
+        self.target = target
+        self.relationship = relationship
+        self.__string = '{0} - {1} - {2}'.format(self.source,
+                                                 self.relationship,
+                                                 self.target)
+
+    def __repr__(self):
+        return str('<Edge: {0}>').format(self.__string)
+
+    def __str__(self):
+        return str('{0}').format(self.__string)
+
+    def __unicode__(self):
+        return self.__string
diff --git a/pypuppetdb/utils.py b/pypuppetdb/utils.py
new file mode 100644
index 0000000..0b73d73
--- /dev/null
+++ b/pypuppetdb/utils.py
@@ -0,0 +1,43 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import warnings
+import datetime
+
+
+# A UTC class, see:
+# http://docs.python.org/2/library/datetime.html#tzinfo-objects
+class UTC(datetime.tzinfo):
+    """UTC"""
+
+    def utcoffset(self, dt):
+        return datetime.timedelta(0)
+
+    def tzname(self, dt):
+        return str('UTC')
+
+    def dst(self, dt):
+        return datetime.timedelta(0)
+
+    def __repr__(self):
+        return str('<UTC>')
+
+    def __str__(self):
+        return str('UTC')
+
+    def __unicode__(self):
+        return 'UTC'
+
+
+def json_to_datetime(date):
+    """Tranforms a JSON datetime string into a timezone aware datetime
+    object with a UTC tzinfo object.
+
+    :param date: The datetime representation.
+    :type date: :obj:`string`
+
+    :returns: A timezone aware datetime object.
+    :rtype: :class:`datetime.datetime`
+    """
+    return datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%fZ').replace(
+        tzinfo=UTC())
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..3cfb4d5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,15 @@
+[pytest]
+norecursedirs = docs .tox
+minversion = 2.3
+markers = 
+	unit: mark test as a unit test. Runs without PuppetDB.
+	integration: mark test as an integration test. Needs a live PuppetDB with testdata loaded.
+
+[wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..6f5df4b
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,62 @@
+import sys
+import os
+import codecs
+
+from setuptools import setup, find_packages
+from setuptools.command.test import test as TestCommand
+
+
+if sys.argv[-1] == 'publish':
+    os.system('python setup.py sdist upload')
+    sys.exit()
+
+
+class Tox(TestCommand):
+
+    def finalize_options(self):
+        TestCommand.finalize_options(self)
+        self.test_args = []
+        self.test_suite = True
+
+    def run_tests(self):
+        #import here, cause outside the eggs aren't loaded
+        import tox
+        errno = tox.cmdline(self.test_args)
+        sys.exit(errno)
+
+with codecs.open('README.rst', encoding='utf-8') as f:
+    README = f.read()
+
+with codecs.open('CHANGELOG.rst', encoding='utf-8') as f:
+    CHANGELOG = f.read()
+
+setup(
+    name='pypuppetdb',
+    version='0.1.1',
+    author='Daniele Sluijters',
+    author_email='daniele.sluijters+pypi at gmail.com',
+    packages=find_packages(),
+    url='https://github.com/nedap/pypuppetdb',
+    license='Apache License 2.0',
+    description='Library for working with the PuppetDB REST API.',
+    long_description='\n'.join((README, CHANGELOG)),
+    keywords='puppet puppetdb',
+    tests_require=['tox'],
+    cmdclass={'test': Tox},
+    install_requires=[
+        "requests >= 1.2.3",
+        ],
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'Natural Language :: English',
+        'License :: OSI Approved :: Apache Software License',
+        'Operating System :: POSIX',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.3',
+        'Topic :: Software Development :: Libraries'
+        ],
+    )

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-puppet/python-pypuppetdb.git



More information about the Pkg-puppet-devel mailing list