beanbag-1.9.2/0002755000175000017500000000000012506516023012112 5ustar ajaj00000000000000beanbag-1.9.2/docs/0002755000175000017500000000000012506516023013042 5ustar ajaj00000000000000beanbag-1.9.2/docs/v1.rst0000644000175000017500000000366312502777435014144 0ustar ajaj00000000000000.. module:: beanbag.v1 beanbag.v1 -- Original-style REST API access ============================================ Setup: .. code:: python >>> import beanbag.v1 as beanbag >>> foo = beanbag.BeanBag("http://hostname/api/") To do REST queries, then: .. code:: python >>> r = foo.resource(p1=3.14, p2=2.718) # GET request >>> r = foo.resource( {"a": 3, "b": 7} ) # POST request >>> del foo.resource # DELETE request >>> foo.resource = {"a" : 7, "b": 3} # PUT request >>> foo.resource += {"a" : 7, "b": 3} # PATCH request You can chain paths as well: .. code:: python >>> print(foo.bar.baz[3]["xyzzy"].q) http://hostname/api/foo/bar/baz/3/xyzzy/q To do a request on a resource that requires a trailing slash: .. code:: python >>> print(foo.bar._) http://hostname/api/foo/bar/ >>> print(foo.bar[""]) http://hostname/api/foo/bar/ >>> print(foo.bar["/"]) http://hostname/api/foo/bar/ >>> print(foo["bar/"]) http://hostname/api/foo/bar/ >>> print(foo.bar._.x == foo.bar.x) True >>> print(foo.bar["_"]) http://hostname/api/foo/bar/_ To access REST interfaces that require authentication, you need to specify a session object. BeanBag supplies helpers to make Kerberos and OAuth 1.0a authentication easier. To setup oauth using OAuth1 directly: .. code:: python >>> import requests >>> from requests_oauth import OAuth1 >>> session = requests.Session() >>> session.auth = OAuth1( consumer creds, user creds ) >>> foo = beanbag.BeanBag("http://hostname/api/", session=session) Using the ``OAuth10aDance`` helper is probably a good plan though. BeanBag class ------------- .. autoclass:: BeanBag :members: __init__, __str__, __getattr__, __getitem__, __call__, __setattr__, __setitem__, __delattr__, __delitem__, __iadd__ :member-order: bysource BeanBagException ---------------- .. autoexception:: BeanBagException :members: :special-members: beanbag-1.9.2/docs/make.bat0000644000175000017500000001505712502731370014455 0ustar ajaj00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\beanbag.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\beanbag.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end beanbag-1.9.2/docs/attrdict.rst0000644000175000017500000000213412506161066015413 0ustar ajaj00000000000000.. module:: beanbag.attrdict beanbag.attrdict -- Access dict members by attribute ==================================================== AttrDict -------- This module provides the ``AttrDict`` class, which allows you to access dict members via attribute access, allowing similar syntax to javascript objects. For example: .. code:: python d = {"foo": 1, "bar": {"sub": {"subsub": 2}}} ad = AttrDict(d) assert ad["foo"] == ad["foo"] assert ad.foo == 1 assert ad.bar.sub.subsub == 2 Note that ``AttrDict`` simply provides a view on the native dict. That dict can be obtained using the plus operator like so: .. code:: python ad = AttrDict(d) assert +ad is d This allows use of native dict methods such as ``d.update()`` or ``d.items()``. Note that attribute access binds more tightly than plus, so brackets will usually need to be used, eg: ``(+ad.bar).items()``. An ``AttrDict`` can also be directly used as an iterator (``for key in attrdict: ...``) and as a container (``if key in attrdict: ...``). .. autoclass:: AttrDict :members: :exclude-members: .base :special-members: beanbag-1.9.2/docs/auth.rst0000644000175000017500000000125712502777435014554 0ustar ajaj00000000000000.. module:: beanbag.auth beanbag.auth -- Authentication Helpers ====================================== Kerberos Helper --------------- To setup kerberos auth: .. code:: python >>> import requests >>> session = requests.Session() >>> session.auth = beanbag.KerbAuth() >>> foo = beanbag.BeanBag("http://hostname/api/", session=session) .. autoclass:: KerbAuth :members: __init__ :undoc-members: OAuth 1.0a Helper ----------------- ``OAuth10aDance`` helps with determining the user creds, compared to using OAuth1 directly. .. autoclass:: OAuth10aDance :members: __init__, get_auth_url, have_creds, oauth, obtain_creds, verify_user :member-order: bysource beanbag-1.9.2/docs/v2.rst0000644000175000017500000000766612506276660014152 0ustar ajaj00000000000000.. module:: beanbag.v2 beanbag.v2 -- REST API access ============================= A quick example: .. code:: python >>> from beanbag.v2 import BeanBag, GET >>> gh = BeanBag("https://api.github.com/") >>> watchers = GET(gh.repos.ajtowns.beanbag.watchers) >>> for w in watchers: ... print(w.login) Setup: .. code:: python >>> import beanbag.v2 as beanbag >>> from beanbag.v2 import GET, POST, PUT, PATCH, DELETE >>> myapi = beanbag.BeanBag("http://hostname/api/") To constuct URLs, you can use attribute-style access or dict-style access: .. code:: python >>> print(myapi.foo) http://hostname/api/foo >>> print(myapi["bar"]) http://hostname/api/bar You can chain paths as well: .. code:: python >>> print(myapi.foo.bar["baz"][3].xyzzy) http://hostname/api/foo/bar/baz/3/xyzzy To do a request on a resource that requires a trailing slash: .. code:: python >>> print(myapi.foo._) http://hostname/api/foo/ >>> print(myapi.foo[""]) http://hostname/api/foo/ >>> print(myapi.foo["/"]) http://hostname/api/foo/ >>> print(myapi["foo/"]) http://hostname/api/foo/ >>> print(myapi.foo._.x == myapi.foo.x) True >>> print(myapi.foo["_"]) http://hostname/api/foo/_ You can add URL parameters using function calls: .. code:: python >>> print(myapi.foo(a=1, b="foo")) http://hostname/api/foo?a=1;b=foo Finally, to actually do REST queries on these queries you can use the GET, POST, PUT, PATCH and DELETE functions. The first argument should be a BeanBag url, and the second argument (if provided) should be the request body, which will be json encoded before being sent. The return value is the request's response (decoded from json). .. code:: python >>> res = GET( foo.resource ) >>> res = POST( foo.resource, {"a": 12} ) >>> DELETE( foo.resource ) To access REST interfaces that require authentication, you need to specify a session object when instantiating the BeanBag initially. BeanBag supplies helpers to make Kerberos and OAuth 1.0a authentication easier. BeanBag class ------------- The BeanBag class does all the magic described above, using ``beanbag.namespace``. .. autoclass:: BeanBag :members: :exclude-members: .base :member-order: bysource :special-members: This class can be subclassed. In particular, if you need to use something other than JSON for requests or responses, subclassing ``BeanBagBase`` and overriding the ``encode`` and ``decode`` methods is probably what you want to do. One caveat: due to the way ``beanbag.namespace`` works, if you wish to invoke the parent classes method, you'll usually need the parent `base` class, accessed via ``~BeanBag`` or ``super(~SubClass, self)``. HTTP Verbs ---------- Functions are provided for the standard set of HTTP verbs. .. autofunction:: GET .. autofunction:: HEAD .. autofunction:: POST .. autofunction:: PUT .. autofunction:: PATCH .. autofunction:: DELETE The verb function is used to create BeanBag compatible verbs. It is used as: .. code:: python GET = verb("GET") .. autofunction:: verb Request ------- The ``Request`` class serves as a place holder for arguments to ``requests.Session.request``. Normally this is constructed from a dict object passed to ``POST`` or ``PUT`` via ``json.dumps()`` however a ``Request`` object can also be created by hand and passed in as the ``body`` parameter in a ``POST`` or similar ``BeanBag`` request. For example to make a POST request with a body that isn't valid JSON: .. code:: python POST(bbexample.path.to.resource, Request(body="MAGIC STRING")) This can be useful with GET requests as well, even though GET requests don't have a body per se: .. code:: python GET(bbexample.path.to.resource, Request(headers={"X-Magic": "String"})) .. autoclass:: Request :members: __init__ :show-inheritance: :undoc-members: :special-members: BeanBagException ---------------- .. autoexception:: BeanBagException :members: :special-members: beanbag-1.9.2/docs/index.rst0000644000175000017500000000314312505207331014700 0ustar ajaj00000000000000.. beanbag documentation master file, created by sphinx-quickstart on Tue Mar 17 15:11:56 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. BeanBag ======= .. module:: beanbag BeanBag is a set of modules that provide some syntactic sugar to make interacting with REST APIs easy and pleasant. A simple example: .. code:: python >>> import beanbag # version 1 api >>> github = beanbag.BeanBag("https://api.github.com") >>> watchers = github.repos.ajtowns.beanbag.watchers() >>> for w in watchers: ... print(w["login"]) .. code:: python >>> import beanbag.v2 as beanbag # version 2 api >>> github = beanbag.BeanBag("https://api.github.com") >>> watchers = GET(github.repos.ajtowns.beanbag.watchers) >>> for w in watchers: ... print(w.login) Contents -------- .. toctree:: :maxdepth: 2 v2.rst v1.rst auth.rst attrdict.rst namespace.rst examples.rst Credits ------- Code contributors: - Anthony Towns - Gary Martin - Lubos Kocman - Daniel Mach Documentation contributors: - Anthony Towns Test case contributors and bug reporters: - Anthony Towns - Russell Stuart BeanBag is inspired by Kadir Pekel's Hammock, though sadly only shares a license, and not any actual code. Hammock is available from `https://github.com/kadirpekel/hammock`. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` beanbag-1.9.2/docs/namespace.rst0000644000175000017500000001205312506161066015532 0ustar ajaj00000000000000.. module:: beanbag.namespace beanbag.namespace ================= The beanbag.namespace module allows defining classes that provide arbitrary namespace behaviour. This is what allows the other beanbag modules to provide their clever syntactic sugar. A entry in a namespace is identified by two components: a base and a path. The base is constructed once for a namespace and is common to all entries in the namespace, and each entry's path is used to differentiate them. For example, with ``AttrDict``, the base is the underlying dictionary (``d``), while the path is the sequence of references into that dictionary (eg, ``("foo", "bar")`` corresponding to ``d["foo"]["bar"]``). The reason for splitting these apart is mostly efficiency -- the path element needs to be cheap and easy to construct and copy since that may need to happen for an attribute access. To define a namespace you provide a class that inherits from ``beanbag.namespace.Namespace`` and defines the methods the base class should have. The ``NamespaceMeta`` metaclass then creates a new base class containing these methods, and builds the namespace class on top of that base class, mapping Python's special method names to the corresponding base class methods, minus the underscores. For example, to define the behavour of the ``~`` operator (aka ``__invert__(self)``), the Base class defines a method: .. code:: python def invert(self, path): ... The code can rely on the base value being ``self``, and the path being ``path``, then do whatever calculation is necessary to create a result. If that result should be a different entry in the same namespace, that can be created by invoking ``self.namespace(newpath)``. In order to make inplace operations work more smoothly, returning ``None`` from those options will be automatically treated as returning the original namespace object (ie ``self.namespace(path)``, without the overhead of reconstructing the object). This is primarily to make it easier to avoid the "double setting" behaviour of python's inplace operations, ie where ``a[i] += j`` is converted into: .. code:: python tmp = a.__getitem__(i) # tmp = a[i] res = tmp.__iadd__(j) # tmp += j a.__setitem__(i, res) # a[i] = tmp In particular, implementations of ``setitem`` and ``setattr`` can avoid poor behaviour here by testing whether the value being set (``res``) is already the existing value, and performing a no-op if so. The ``SettableHierarchialNS`` class implements this behaviour. NamespaceMeta ------------- The ``NamespaceMeta`` metaclass provides the magic for creating arbitrary namespaces from Base classes as discussed above. When set as the metaclass for a class, it will turn a base class into a namespace class directly, while constructing an appropriate base class for the namespace to use. .. autoclass:: NamespaceMeta :members: :special-members: :undoc-members: NamespaceBase ------------- The generated base class will inherit from ``NamespaceBase`` (or the base class corresponding to any namespaces the namespace class inherits from), and will have a ``Namespace`` attribute referencing the namespace class. Further, the generated base class can be accessed by using the inverse opertor on the namespace class, ie ``MyNamespaceBase = ~MyNamespace``. .. autoclass:: NamespaceBase :members: Namespace --------- ``Namespace`` provides a trivial Base implementation. It's primarily useful as a parent class for inheritance, so that you don't have explicitly set ``NamespaceMeta`` as your metaclass. .. autoclass:: Namespace :members: HierarchialNS ------------- ``HierarchialNS`` provides a simple basis for producing namespaces with freeform attribute and item hierarchies, eg, where you might have something like ``ns.foo.bar["baz"]``. By default, this class specifies a path as a tuple of attributes, but this can be changed by overriding the ``path`` and ``_get`` methods. If some conversion is desired on either attribute or item access, the ``attr`` and ``item`` methods can be overridden respectively. Otherwise, to get useful behaviour from this class, you probably want to provide some additional methods, such as ``__call__``. .. autoclass:: HierarchialNS :exclude-members: .base :show-inheritance: :members: :special-members: :undoc-members: SettableHierarchialNS --------------------- ``SettableHierarchialNS`` is intended to make life slightly easier if you want to be able to assign to your hierarchial namespace. It provides ``set`` and ``delete`` methods that you can implement, without having to go to the trouble of implementing both item and attribute variants of both functions. This class implements the check for "setting to self" mentioned earlier in order to prevent inplace operations having two effects. It uses the ``eq`` method to test for equality. .. autoclass:: SettableHierarchialNS :show-inheritance: :exclude-members: .base :members: :special-members: :undoc-members: sig_adapt --------- This is a helper function to make that generated methods in the namespace object provide more useful help. .. autofunction:: sig_adapt beanbag-1.9.2/docs/conf.py0000644000175000017500000001775412504056611014355 0ustar ajaj00000000000000# -*- coding: utf-8 -*- # # beanbag documentation build configuration file, created by # sphinx-quickstart on Tue Mar 17 15:11:56 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'beanbag' copyright = u'2015, Anthony Towns' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.9' # The full version, including alpha/beta/rc tags. release = '1.9.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'beanbagdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'beanbag.tex', u'beanbag Documentation', u'Anthony Towns', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'beanbag', u'beanbag Documentation', [u'Anthony Towns'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'beanbag', u'beanbag Documentation', u'Anthony Towns', 'beanbag', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False beanbag-1.9.2/docs/examples.rst0000644000175000017500000000443512505257723015426 0ustar ajaj00000000000000 Examples ======== What follows are some examples of using BeanBag for various services. GitHub ------ `GitHub's REST API`_, using JSON for data and either HTTP Basic Auth or OAuth2 for authentication. Basic Auth is perfect for a command line app, since the user can just use their github account password directly. .. _Github's REST API: https://developer.github.com/v3/ The following example uses the github API to list everyone who's starred one of your repos, and which repo it is that they've starred. .. literalinclude:: ../examples/github :language: python Twitter ------- `Twitter's REST API`_ is slightly more complicated. It still uses JSON, but requires OAuth 1.0a to be used for authentication. OAuth is designed primarily for webapps, where the application is controlled by a third party. In particular it is designed to allow an "application" to authenticate as "authorised by a particular user", rather than allowing the application to directly authenticate itself as the user (eg, by using the user's username and password directly, as we did above with github). This in turn means that the application has to be able to identify itself. This is done by gaining "client credential", in Twitter's case via `Twitter Apps`_. .. _Twitter's REST API: https://dev.twitter.com/rest/public .. _Twitter Apps: https://apps.twitter.com/ The process of having an application to ask a user to provide a token that allows it to access Twitter on behalf of the user is encapsulated in the ``OAuth10aDance`` class. In the example below is subclassed in order to provide the Twitter-specific URLs that the user and application will need to visit in order to gain the right tokens to do the authentication. The ``obtain_creds()`` method is called, which will instruct the user to enter any necessary credentials, after which a ``Session`` object is created and setup to perform OAuth authentication using the provided credentials. The final minor complication is that Twitter's endpoints all end with ".json", which would be annoying to have to specify via beanbag (since "." is not a valid part of an attribute). The ``ext=`` keyword argument of the ``BeanBag`` constructor is used to supply this as the standard extension for all URLs in the Twitter API. .. literalinclude:: ../examples/twitter :language: python beanbag-1.9.2/docs/Makefile0000644000175000017500000001515612502731370014510 0ustar ajaj00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/beanbag.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/beanbag.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/beanbag" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/beanbag" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." beanbag-1.9.2/beanbag.egg-info/0002755000175000017500000000000012506516023015163 5ustar ajaj00000000000000beanbag-1.9.2/beanbag.egg-info/top_level.txt0000644000175000017500000000001012506516022017701 0ustar ajaj00000000000000beanbag beanbag-1.9.2/beanbag.egg-info/requires.txt0000644000175000017500000000002312506516022017553 0ustar ajaj00000000000000requests >= 0.12.1 beanbag-1.9.2/beanbag.egg-info/SOURCES.txt0000644000175000017500000000132712506516023017050 0ustar ajaj00000000000000.gitignore LICENSE README.rst setup.py beanbag/__init__.py beanbag/attrdict.py beanbag/auth.py beanbag/bbexcept.py beanbag/namespace.py beanbag/url_v1.py beanbag/v1.py beanbag/v2.py beanbag.egg-info/PKG-INFO beanbag.egg-info/SOURCES.txt beanbag.egg-info/dependency_links.txt beanbag.egg-info/requires.txt beanbag.egg-info/top_level.txt debian/changelog debian/compat debian/control debian/copyright debian/python-beanbag-doc.install debian/rules debian/source/format docs/Makefile docs/attrdict.rst docs/auth.rst docs/conf.py docs/examples.rst docs/index.rst docs/make.bat docs/namespace.rst docs/v1.rst docs/v2.rst examples/github examples/twitter tests/fake_req.py tests/test_attrdict.py tests/test_bbv1.py tests/test_bbv2.pybeanbag-1.9.2/beanbag.egg-info/PKG-INFO0000644000175000017500000000170012506516022016253 0ustar ajaj00000000000000Metadata-Version: 1.1 Name: beanbag Version: 1.9.2 Summary: A helper module for accessing REST APIs Home-page: https://github.com/ajtowns/beanbag Author: Anthony Towns Author-email: aj@erisian.com.au License: MIT Description: BeanBag ======= BeanBag is a simple module that lets you access REST APIs in an easy way. For example: >>> import beanbag >>> github = beanbag.BeanBag("https://api.github.com") >>> watchers = github.repos.ajtowns.beanbag.watchers() >>> for w in watchers: ... print(w["login"]) See `http://beanbag.readthedocs.org/` for more information. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License beanbag-1.9.2/beanbag.egg-info/dependency_links.txt0000644000175000017500000000000112506516022021226 0ustar ajaj00000000000000 beanbag-1.9.2/beanbag/0002755000175000017500000000000012506516023013471 5ustar ajaj00000000000000beanbag-1.9.2/beanbag/__init__.py0000644000175000017500000000025012504056617015603 0ustar ajaj00000000000000#!/usr/bin/env python # Copyright (c) 2015 Anthony Towns # Written by Anthony Towns # See LICENSE file. __version__ = '1.9.2' from .v1 import * beanbag-1.9.2/beanbag/v1.py0000644000175000017500000000047712502777435014413 0ustar ajaj00000000000000#!/usr/bin/env python # Copyright (c) 2015 Anthony Towns # Written by Anthony Towns # See LICENSE file. __all__ = ['BeanBag', 'BeanBagException', 'KerbAuth', 'OAuth10aDance'] from .url_v1 import BeanBag from .bbexcept import BeanBagException from .auth import KerbAuth, OAuth10aDance beanbag-1.9.2/beanbag/bbexcept.py0000644000175000017500000000171212506266442015645 0ustar ajaj00000000000000#!/usr/bin/env python # Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. # Copyright (c) 2015 Anthony Towns # Written by Anthony Towns # See LICENSE file. class BeanBagException(Exception): """Exception thrown when a BeanBag request fails. Data members: * msg -- exception string, brief and human readable * response -- response object You can get the original request via bbe.response.request. """ __slots__ = ('msg', 'response') def __init__(self, response, msg): """Create a BeanBagException""" self.msg = msg self.response = response def __repr__(self): return "%s(%s,%r)" % (self.__class__.__name__, self.msg, self.response) def __str__(self): msg = self.msg if self.response and hasattr(self.response, "content"): msg = "%s - response: %s" % (self.msg, self.response.content) return msg beanbag-1.9.2/beanbag/attrdict.py0000644000175000017500000000430512506161066015664 0ustar ajaj00000000000000#!/usr/bin/env python from . import namespace class AttrDict(namespace.SettableHierarchialNS): """Allow access to dictionary via attributes as well as array-style references.""" def __init__(self, base=None): """Provide an AttrDict view of a dictionary. :param base: dictionary/list to be viewed """ if base is None: self.base = {} else: self.base = base def repr(self, path): return "<%s(%s)>" % (self.Namespace.__name__, ".".join(map(str, path))) def item(self, item): return item def descend(self, path, create=True): base = self.base for p in path: try: base = base[p] except: if isinstance(create, type) and issubclass(create, Exception): raise create(p) elif create and isinstance(base, dict): base[p] = {} base = base[p] elif not create: return None else: raise return base def pos(self, path): """View underlying dict object""" return self.descend(path, create=KeyError) def str(self, path): return str(self.pos(path)) def get(self, path): o = self.descend(path, create=False) if isinstance(o, dict) or isinstance(o, list) or o is None: return self.namespace(path) else: return o def set(self, path, val): o = self.descend(path[:-1]) o[path[-1]] = val def delete(self, path): o = self.descend(path[:-1], create=KeyError) del o[path[-1]] def eq(self, path, other): """self == other""" try: return other == (self.pos(path)) except KeyError: return False def contains(self, path, val): return val in self.pos(path) def iter(self, path): p = self.pos(path) if isinstance(p, list): return (self.namespace(path + (i,)) for i in range(len(p))) else: return self.pos(path).__iter__() def len(self, path): return self.pos(path).__len__() beanbag-1.9.2/beanbag/v2.py0000644000175000017500000001646212506500214014374 0ustar ajaj00000000000000#!/usr/bin/env python # Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. # Copyright (c) 2015 Anthony Towns # Written by Anthony Towns # See LICENSE file. from .namespace import HierarchialNS from .bbexcept import BeanBagException from .attrdict import AttrDict import requests try: import json except ImportError: import simplejson as json __all__ = ['BeanBag', 'Request', 'verb', 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'] __version__ = '2.0.0' def verb(verbname): """Construct a BeanBag compatible verb function :param verbname: verb to use (GET, POST, etc) """ def do(url, body=None): base, path = ~url req = base.encode(body) res = base.make_request(path, verbname, req) return base.decode(res) do.__name__ = verbname do.__doc__ = "%s verb function" % (verbname,) return do GET = verb("GET") HEAD = verb("HEAD") POST = verb("POST") PUT = verb("PUT") PATCH = verb("PATCH") DELETE = verb("DELETE") class Request(AttrDict): def __init__(self, **kwargs): """Create a Request object Request objects act as placeholders for the arguments to the requests() function of the requests.Session being used. They are used as the interface between the encode() and make_request() functions, and may also be used by the API caller. NB: A Request object is only suitable for one use, as it may be modified in-place during the request. For this reason, __init__ makes a (shallow) copy of all the keyword arguments supplied rather than using them directly. """ for badarg in ["method", "url", "params"]: if badarg in kwargs: raise TypeError("__init__() got forbidden keyword argument '%s'" % (badarg,)) d = {} for k, v in kwargs.items(): if isinstance(v, list): v = v[:] elif isinstance(v, dict): v = v.copy() d[k] = v (~AttrDict).__init__(self, d) class BeanBag(HierarchialNS): mime_json = "application/json" def __init__(self, base_url, ext="", session=None, use_attrdict=True): """Create a BeanBag referencing a base REST path. :param base_url: the base URL prefix for all resources :param ext: extension to add to resource URLs, eg ".json" :param session: requests.Session instance used for this API. Useful to set an auth procedure, or change verify parameter. :param use_attrdict: if true, ``decode()`` will wrap dicts and lists in a ``beanbag.attrdict.AttrDict`` for syntactic sugar. """ if session is None: session = requests.Session() self.base_url = base_url.rstrip("/") + "/" self.ext = ext self.session = session self.use_attrdict = use_attrdict def encode(self, body): """Convert a python object into a beanbag.Request object. This function converts the user provided body object (or None when there is no body) into a requests.Request object, by encoding it as JSON string. (Note that the url and method members of the Request are provided later by the :param body: provided by the API user, usually a dict or None """ if isinstance(body, Request): req = body elif body is None: req = Request(data=None, headers={"Accept": self.mime_json}) else: if isinstance(body, AttrDict): body = +body req = Request(data=json.dumps(body), headers={"Accept": self.mime_json, "Content-Type": self.mime_json}) return req def decode(self, response): """Converts a requests.Response object to a python object This function converts the REST API's response back into a python object by decoding it from JSON (or raises an exception if the response indicates an error). :param response: requests.Response object """ if response.status_code < 200 or response.status_code >= 300: raise BeanBagException(response, "Bad response code: %d" % (response.status_code,)) if not response.content: return None res_content = response.headers.get("content-type", None) if res_content is None: pass elif res_content.split(";", 1)[0] == self.mime_json: pass else: raise BeanBagException(response, "Bad content-type in response (Content-Type: %s; wanted %s)" % (res_content.split(";", 1)[0], self.mime_json)) try: obj = json.loads(response.text or response.content) except: raise BeanBagException(response, "Could not decode response") if self.use_attrdict: if isinstance(obj, dict) or isinstance(obj, list): obj = AttrDict(obj) return obj def baseurl_params(self, path): """Construct the base URL of a resource (excluding URL params)""" url, params = path return ("%s%s%s" % (self.base_url, url, self.ext)), params def str(self, path): """Obtain the URL of a resource""" url, params = self.baseurl_params(path) if params: url = "%s?%s" % (url, ";".join("%s=%s" % (str(k), str(v)) for k, v in params.items() if v is not None)) return url def path(self): return ("", {}) def attr(self, attr): """Special processing for attribute access This converts ._ to a trailing slash. """ if attr == "_": attr = "/" return str(attr) def _get(self, path, el): url, params = path el = str(el).lstrip("/") if url == "": newurl = el else: newurl = url.rstrip("/") + "/" + el return (newurl, params) def call(self, path, *args, **kwargs): """Set URL parameters""" url, params = path newparams = params.copy() for a in tuple(args) + (kwargs,): for k, v in a.items(): if v is not None: newparams[k] = v elif k in newparams: del newparams[k] return self.namespace((url, newparams)) def invert(self, path): """Provide access to the base/path via the namespace object .. code:: bb = BeanBag(...) base, path = ~bb.foo assert isinstance(base, BeanBagBase) This is the little bit of glue needed so that it's possible to call methods defined in BeanBagBase directly rather than just the operators BeanBag supports. """ return self, path def make_request(self, path, verb, request): """Make a REST request to a resource""" url, params = self.baseurl_params(path) assert isinstance(request, Request) request = +request # convert to dictionary return self.session.request( method=verb, url=url, params=params, **request) beanbag-1.9.2/beanbag/namespace.py0000644000175000017500000002340512506266565016016 0ustar ajaj00000000000000#!/usr/bin/env python import inspect import functools import sys def sig_adapt(sigfn, dropargs=None, name=None): """Function decorator that changes the name and (optionally) signature of a function to match another function. This is useful for making the help of generic wrapper functions match the functions they're wrapping. For example: .. code:: def foo(a, b, c, d=None): pass @sig_adapt(foo) def myfn(*args, **kwargs): pass The optional "name" parameter allows renaming the function to something different to the original function's name. The optional "dropargs" parameter allows dropping arguments by position or name. (Note positions are 0 based, so to convert foo(self, a, b) to foo(a, b) specify dropargs=("self",) or dropargs=(0,)) """ # Python 3.3+, PEP 362 if hasattr(inspect, "signature"): def adapter(fn): sig = inspect.signature(sigfn) if dropargs is not None: newparams = [p for i, (name, p) in enumerate(sig.parameters.items()) if i not in dropargs and name not in dropargs] sig = sig.replace(parameters=newparams) functools.update_wrapper(fn, sigfn) if name is not None: fn.__name__ = name fn.__signature__ = sig return fn return adapter # Pre Python 3.3 def adapter(fn): spec = list(inspect.getargspec(sigfn)) if dropargs is not None: posargs = [arg for i, arg in enumerate(spec[0]) if i not in dropargs and arg not in dropargs] if len(spec) >= 4 and spec[3]: odefs = spec[3] nodefs = len(spec[0]) - len(odefs) defs = [odefs[i] for i, arg in enumerate(spec[0][-len(odefs):]) if i + nodefs not in dropargs and arg not in dropargs] else: defs = [] spec = [posargs, spec[1], spec[2], defs] fargs = inspect.formatargspec(*spec) fargs = fargs.lstrip("(").rstrip(")") bfargs = inspect.formatargspec(*(spec[:3])) # eval is the only way to preserve function signature # prior to PEP 362 included in py3.3 # (note that bfargs needs to drop any default values for arguments) l = "lambda %s: fn%s" % (fargs, bfargs) fn = eval(l, dict(fn=fn)) functools.update_wrapper(fn, sigfn) if name is not None: fn.__name__ = name return fn return adapter class NamespaceMeta(type): # attr ops are special because we have magic ".base" and ".path" attributes ops_attr = ["getattr", "setattr", "delattr"] # number ops are special since they have reverse and inplace variants __ops_num = "add sub mul pow div floordiv lshift rshift and or xor".split() ops_inum = ["i" + _x for _x in __ops_num] # other ops ops = ("repr str call bool" # standard " getitem setitem delitem len iter reversed contains" # container " enter exit" # context " pos neg invert" # unary " eq ne lt le gt ge" # comparsion # "cmp rcmp hash unicode", # maybe should do these too? ).split() + __ops_num + ["r" + _x for _x in __ops_num] def __new__(mcls, name, bases, nmspc): basebases = tuple(~cls for cls in bases if isinstance(cls, mcls)) if not basebases: basebases = (NamespaceBase,) qn = None if "__qualname__" in nmspc: qn = nmspc["__qualname__"] nmspc["__qualname__"] = qn + "Base" basecls = type.__new__(type, name + "Base", basebases, nmspc) conv_nmspc = mcls.make_namespace(basecls) if "__module__" in nmspc: conv_nmspc["__module__"] = nmspc["__module__"] if qn is not None: conv_nmspc["__qualname__"] = qn cls = type.__new__(mcls, name, bases, conv_nmspc) basecls.Namespace = cls return cls def __invert__(cls): """Obtain base class for namespace""" return getattr(cls, ".base") @staticmethod def wrap_path_fn(basefn): def fn(self, *args, **kwargs): return basefn(getattr(self, ".base"), getattr(self, ".path"), *args, **kwargs) return fn @staticmethod def wrap_path_fn_inum(basefn): def fn(self, *args, **kwargs): r = basefn(getattr(self, ".base"), getattr(self, ".path"), *args, **kwargs) if r is None: r = self return r return fn @staticmethod def wrap_path_fn_attr(basefn): def fn(self, attr, *args, **kwargs): if attr.startswith("."): return object.__setattr__(self, attr, *args, **kwargs) return basefn(getattr(self, ".base"), getattr(self, ".path"), attr, *args, **kwargs) return fn @classmethod def deferfn(mcls, cls, nsdict, basefnname, inum=False, attr=False): if not hasattr(cls, basefnname): return # not implemented so nothing to do basefn = getattr(cls, basefnname) if inum: fn = mcls.wrap_path_fn_inum(basefn) elif attr: fn = mcls.wrap_path_fn_attr(basefn) else: fn = mcls.wrap_path_fn(basefn) fname = "__%s__" % (basefnname,) if basefnname == "bool" and sys.version_info[0] == 2: fname = "__nonzero__" fn = sig_adapt(basefn, dropargs=(1,), name=fname)(fn) nsdict[fname] = fn @classmethod def make_namespace(mcls, cls): """create a unique Namespace class based on provided class""" clsnmspc = {} for op in mcls.ops: mcls.deferfn(cls, clsnmspc, op) for op in mcls.ops_inum: mcls.deferfn(cls, clsnmspc, op, inum=True) for op in mcls.ops_attr: mcls.deferfn(cls, clsnmspc, op, attr=True) def init(self, *args, **kwargs): b = cls(*args, **kwargs) setattr(self, ".base", b) setattr(self, ".path", b.path()) if "__init__" in cls.__dict__: init = sig_adapt(cls.__init__)(init) clsnmspc["__init__"] = init clsnmspc[".base"] = cls return clsnmspc class NamespaceBase(object): """Base class for user-defined namespace classes' bases""" Namespace = None """Replaced in subclasses by the corresponding namespace class""" def namespace(self, path=None): """Used to create a new Namespace object from the Base class""" if path is None: path = self.path() r = self.Namespace.__new__(self.Namespace) setattr(r, ".base", self) setattr(r, ".path", path) return r # setup base class so can use metaclass via inheritance # (this is the only common syntax for using metaclasses that works # with both py2 and py3) Namespace = NamespaceMeta.__new__(NamespaceMeta, "Namespace", (object,), {}) class HierarchialNS(Namespace): def __init__(self): pass def path(self): """Returns empty path""" return () def _get(self, path, el): """Returns updated path when new component is added""" return path + (el,) def str(self, path): """Returns path joined by dots""" return ".".join(str(x) for x in path) def repr(self, path): """Human readable representation of object""" return "<%s(%s)>" % (self.__class__.__name__, self.str(path)) def eq(self, path, other): """self == other""" if isinstance(other, self.Namespace): return other.__eq__((self, path)) elif isinstance(other, tuple) and len(other) == 2: oself, opath = other return self is oself and path == opath else: return False def ne(self, path, other): """self != other""" return not self.eq(path, other) def get(self, path): return self.namespace(path) def item(self, item): """Applies any conversion needed for items (self[item])""" return str(item) def attr(self, attr): """Applies any conversion needed for attributes (self.attr)""" return str(attr) def getattr(self, path, attr): """self.attr""" return self.get(self._get(path, self.attr(attr))) def getitem(self, path, item): """self[attr]""" return self.get(self._get(path, self.item(item))) class SettableHierarchialNS(HierarchialNS): def set(self, path, val): """self.path = val or self[path] = val""" raise NotImplementedError def delete(self, path): """del self.path or del self[path]""" raise NotImplementedError def _set(self, path, val): """Helper function to avoid calling self.set() when doing inum ops Normally python will convert: self.path += val into a = self.path self.path = a.__iadd__(val) which is useful if __iadd__ doesn't just return self, but not desirable here """ if self.eq(path, val): return None return self.set(path, val) def setattr(self, path, attr, val): """self.attr = val""" return self._set(self._get(path, self.attr(attr)), val) def setitem(self, path, item, val): """self[item] = val""" return self._set(self._get(path, self.item(item)), val) def delattr(self, path, attr): """del self.attr""" return self.delete(self._get(path, self.attr(attr))) def delitem(self, path, item): """del self[item]""" return self.delete(self._get(path, self.item(item))) beanbag-1.9.2/beanbag/url_v1.py0000644000175000017500000001202012506266611015251 0ustar ajaj00000000000000#!/usr/bin/env python # Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. # Copyright (c) 2015 Anthony Towns # Written by Anthony Towns # See LICENSE file. from __future__ import print_function from .namespace import SettableHierarchialNS from .bbexcept import BeanBagException import requests try: import json except ImportError: import simplejson as json __all__ = ['BeanBag', 'BeanBagException'] class BeanBag(SettableHierarchialNS): def __init__(self, base_url, ext="", session=None, fmt='json'): """Create a BeanBag referencing a base REST path. :param base_url: the base URL prefix for all resources :param ext: extension to add to resource URLs, eg ".json" :param session: requests.Session instance used for this API. Useful to set an auth procedure, or change verify parameter. :param fmt: either 'json' for json data, or a tuple specifying a content-type string, encode function (for encoding the request body) and a decode function (for decoding responses) """ if session is None: session = requests.Session() if fmt == 'json': content_type = "application/json" encode = json.dumps decode = lambda req: json.loads(req.text or req.content) else: content_type, encode, decode = fmt self.base_url = base_url.rstrip("/") + "/" self.ext = ext self.content_type = content_type self.encode = encode self.decode = decode self.session = session self.session.headers["accept"] = self.content_type self.session.headers["content-type"] = self.content_type def str(self, path): """Obtain the URL of a resource""" return self.base_url + path + self.ext def path(self): return "" def attr(self, attr): if attr == "_": attr = "/" return str(attr) def _get(self, path, el): el = str(el).lstrip("/") if path == "": newpath = el else: newpath = path.rstrip("/") + "/" + el return newpath def call(self, path, *args, **kwargs): """Make a GET, POST or generic request to a resource. :Example: >>> x = BeanBag("http://host/api") >>> r = x() # GET request >>> r = x(p1='foo', p2=3) # GET request with parameters passed via query string >>> r = x( {'a': 1, 'b': 2} ) # POST request >>> r = x( "RANDOMIZE", {'a': 1, 'b': 2} ) # Custom HTTP verb with request body >>> r = x( "OPTIONS", None ) # Custom HTTP verb with empty request body """ if len(args) == 0: verb, body = "GET", None elif len(args) == 1: verb, body = "POST", args[0] elif len(args) == 2: verb, body = args else: raise TypeError("__call__ expected up to 2 arguments, got %d" % (len(args))) return self.make_request(path, verb, kwargs, body) def set(self, path, val): """Make a PUT request to a subresource. :Example: >>> x = BeanBag("http://host/api") >>> x.res = {"a": 1} >>> x["res"] = {"a": 1} """ return self.make_request(path, "PUT", {}, val) def delete(self, path): """Make a DELETE request to a subresource. :Example: >>> x = BeanBag("http://host/api") >>> del x.res >>> del x["res"] """ return self.make_request(path, "DELETE", {}, None) def iadd(self, path, val): """Make a PATCH request to a resource. :Example: >>> x = BeanBag("http://host/api") >>> x += {"op": "replace", "path": "/a", "value": 3} """ self.make_request(path, "PATCH", {}, val) return None def make_request(self, path, verb, params, body): path = self.str(path) if body is None: ebody = None else: try: ebody = self.encode(body) except: raise BeanBagException(None, "Could not encode request body") r = self.session.request(verb, path, params=params, data=ebody) if r.status_code < 200 or r.status_code >= 300: raise BeanBagException(r, "Bad response code: %d" % (r.status_code,)) if not r.content: return None ctype = r.headers.get("content-type", self.content_type) ctype = ctype.split(";", 1)[0] if ctype != self.content_type: raise BeanBagException(r, "Bad content-type in response (Content-Type: %s)" % (r.headers["content-type"],)) try: return self.decode(r) except: raise BeanBagException(r, "Could not decode response") beanbag-1.9.2/beanbag/auth.py0000644000175000017500000001531412505235564015015 0ustar ajaj00000000000000#!/usr/bin/env python # Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. # Copyright (c) 2015 Anthony Towns # Written by Anthony Towns # See LICENSE file. from __future__ import print_function import requests try: from urlparse import urlparse, parse_qs except ImportError: from urllib.parse import urlparse, parse_qs try: input = raw_input # rename raw_input for compat with py3 except NameError: pass class KerbAuth(requests.auth.AuthBase): """Helper class for basic Kerberos authentication using requests library. A single instance can be used for multiple sites. Each request to the same site will use the same authorization token for a period of 180 seconds. :Example: >>> session = requests.Session() >>> session.auth = KerbAuth() """ def __init__(self, timeout=180): import time import kerberos self.header_cache = {} self.timeout = timeout self.time = time.time self.kerberos = kerberos def __call__(self, r): hostname = urlparse(r.url).hostname header, last = self.header_cache.get(hostname, (None, None)) if not header or (self.time() - last) >= self.timeout: service = "HTTP@" + hostname rc, vc = self.kerberos.authGSSClientInit(service) self.kerberos.authGSSClientStep(vc, "") header = "negotiate %s" % self.kerberos.authGSSClientResponse(vc) last = self.time() self.header_cache[hostname] = (header, last) r.headers['Authorization'] = header return r class OAuth10aDance(object): __slots__ = [ 'req_token', 'authorize', 'acc_token', # oauth resource URLs 'client_key', 'client_secret', # client creds 'user_key', 'user_secret', # user creds 'OAuth1' # OAuth1 module ref ] def __init__(self, req_token=None, acc_token=None, authorize=None, client_key=None, client_secret=None, user_key=None, user_secret=None): """Create an OAuth10aDance object to negotiatie OAuth 1.0a credentials. The first set of parameters are the URLs to the OAuth 1.0a service you wish to authenticate against. :param req_token: Request token URL :param authorize: User authorization URL :param acc_token: Access token URL These parameters (and the others) may also be provided by subclassing the OAuth10aDance class, eg: :Example: >>> class OAuthDanceTwitter(beanbag.OAuth10aDance): ... req_token = "https://api.twitter.com/oauth/request_token" ... authorize = "https://api.twitter.com/oauth/authorize" ... acc_token = "https://api.twitter.com/oauth/access_token" The second set of parameters identify the client application to the server, and need to be obtained outside of the OAuth protocol. :param client_key: client/consumer key :param client_secret: client/consumer secret The final set of parameters identify the user to server. These may be left as None, and obtained using the OAuth 1.0a protocol via the ``obtain_creds()`` method or using the ``get_auth_url()`` and ``verify_user()`` methods. :param user_key: user key :param user_secret: user secret Assuming OAuthDanceTwitter is defined as above, and you have obtained the client key and secret (see https://apps.twitter.com/ for twitter) as ``k`` and ``s``, then putting these together looks like: :Example: >>> oauthdance = OAuthDanceTwitter(client_key=k, client_secret=s) >>> oauthdance.obtain_creds() Please go to url: https://api.twitter.com/oauth/authorize?oauth_token=... Please input the verifier: 1111111 >>> session = requests.Session() >>> session.auth = oauthdance.oauth() """ from requests_oauthlib import OAuth1 self.OAuth1 = OAuth1 # override instance variables based on parameters for s in self.__slots__: u = locals().get(s, None) if u is not None: setattr(self, s, u) elif not hasattr(self, s): setattr(self, s, None) def have_creds(self): """Check whether all credentials are filled in""" return (self.client_key and self.client_secret and self.user_key and self.user_secret) def get_auth_url(self): """URL for user to obtain verification code""" oauth = self.OAuth1(self.client_key, client_secret=self.client_secret) r = requests.post(url=self.req_token, auth=oauth) credentials = parse_qs(r.content.decode('utf-8')) self.user_key = credentials.get('oauth_token', [""])[0] self.user_secret = credentials.get('oauth_token_secret', [""])[0] return self.authorize + '?oauth_token=' + self.user_key def verify_user(self, verifier): """Set user key and secret based on verification code""" oauth = self.OAuth1(self.client_key, client_secret=self.client_secret, resource_owner_key=self.user_key, resource_owner_secret=self.user_secret, verifier=verifier) r = requests.post(url=self.acc_token, auth=oauth) credentials = parse_qs(r.content.decode('utf-8')) self.user_key = credentials.get('oauth_token', [""])[0] self.user_secret = credentials.get('oauth_token_secret', [""])[0] def obtain_creds(self): """Fill in credentials by interacting with the user (input/print)""" if not self.client_key: self.client_key = input('Please input client key: ') if not self.client_secret: self.client_secret = input('Please input client secret: ') if self.user_key and self.user_secret: return assert self.req_token and self.acc_token and self.authorize print('Please go to url:\n %s' % (self.get_auth_url(),)) verifier = input('Please input the verifier: ') self.verify_user(verifier) print('User key: %s\nUser secret: %s' % (self.user_key, self.user_secret)) def oauth(self): """Create an OAuth1 authenticator using client and user credentials""" assert self.have_creds() return self.OAuth1(self.client_key, client_secret=self.client_secret, resource_owner_key=self.user_key, resource_owner_secret=self.user_secret) beanbag-1.9.2/README.rst0000644000175000017500000000055112502777520013607 0ustar ajaj00000000000000 BeanBag ======= BeanBag is a simple module that lets you access REST APIs in an easy way. For example: >>> import beanbag >>> github = beanbag.BeanBag("https://api.github.com") >>> watchers = github.repos.ajtowns.beanbag.watchers() >>> for w in watchers: ... print(w["login"]) See `http://beanbag.readthedocs.org/` for more information. beanbag-1.9.2/tests/0002755000175000017500000000000012506516023013254 5ustar ajaj00000000000000beanbag-1.9.2/tests/fake_req.py0000644000175000017500000000462312504255510015405 0ustar ajaj00000000000000#!/usr/bin/env python import json class FakeResponse(object): def __init__(self, status_code=200, content={}): if not isinstance(content, str): content = json.dumps(content) self.headers = {"content-type": "application/json"} self.text = self.content = content self.status_code = status_code _Any = object() class FakeSession(object): def __init__(self): self.headers = {} self.expecting = None def expect(self, method, url, params=None, data=None): assert self.expecting is None, "Missed request" self.expecting = (method, url, params, data) def merge_environment_settings(self, *args, **kwargs): return {} def prepare_request(self, req): assert req.json is None and req.files == [] return req def send(self, request, **kwargs): assert not kwargs assert isinstance(request.data, str) or request.data == [] data = request.data if not data: data = None return self.request( method=request.method, url=request.url, params=request.params, data=data, headers=request.headers) def request(self, method, url, params=None, data=None, headers=None): assert self.expecting is not None, \ "Unexpected request: %s %s %s %s" % (method, url, params, data) exp_method, exp_url, exp_params, exp_data = self.expecting self.expecting = None if data is not None: dec_data = json.loads(data) else: dec_data = None if exp_method is _Any: exp_method = method if exp_url is _Any: exp_url = url if exp_params is _Any: exp_params = params if exp_data is _Any: exp_data = dec_data if exp_params is None and params == {}: exp_params = {} if exp_params == {} and params is None: exp_params = None assert exp_method == method and exp_url == url and exp_params == params and exp_data == dec_data res_obj = dict(method=method, url=url, params=params, data=data) status_code = 200 if params and "status" in params: status_code = int(params["status"]) if params and "result" in params: res_obj = str(params["result"]) return FakeResponse(status_code=status_code, content=res_obj) beanbag-1.9.2/tests/test_bbv1.py0000644000175000017500000000200412504255515015515 0ustar ajaj00000000000000#!/usr/bin/env python import py.test import beanbag.v1 as beanbag import json from fake_req import FakeSession, _Any def test_bb(): s = FakeSession() b = beanbag.BeanBag("http://www.example.org/path/", session=s) assert str(b) == "http://www.example.org/path/" s.expect("GET", "http://www.example.org/path/") q = b() s.expect("GET", "http://www.example.org/path/", params=dict(a=1, b=2)) q = b(a=1, b=2) s.expect("PUT", "http://www.example.org/path/") b._ = None s.expect("PATCH", "http://www.example.org/path/") b._ += None s.expect("DELETE", "http://www.example.org/path/") del b._ s.expect("POST", "http://www.example.org/path/foo", data={"a": 1}) q = b.foo(dict(a=1)) s.expect("GET", "http://www.example.org/path/foo", params={"result": "/*INVALID*/"}) try: q = b.foo(result="/*INVALID*/") assert False, "should have raised exception" except beanbag.BeanBagException as e: assert e.msg == "Could not decode response" beanbag-1.9.2/tests/test_bbv2.py0000644000175000017500000000376512506457276015547 0ustar ajaj00000000000000#!/usr/bin/env python import pytest from beanbag.v2 import BeanBag, BeanBagException, GET, POST, PUT, PATCH, DELETE from beanbag.attrdict import AttrDict import json from fake_req import FakeSession, _Any def test_bb(): s = FakeSession() b = BeanBag("http://www.example.org/path/", session=s) assert str(b) == "http://www.example.org/path/" s.expect("GET", "http://www.example.org/path/") GET(b) s.expect("GET", "http://www.example.org/path/", params=dict(a=1, b=2)) GET(b(a=1, b=2)) s.expect("PUT", "http://www.example.org/path/") PUT(b._, None) s.expect("PATCH", "http://www.example.org/path/") PATCH(b._, None) s.expect("DELETE", "http://www.example.org/path/") DELETE(b._) s.expect("POST", "http://www.example.org/path/foo", data={"a": 1}) POST(b.foo, dict(a=1)) s.expect("GET", "http://www.example.org/path/", params=dict(status=300)) with pytest.raises(BeanBagException) as e: GET(b(status=300)) assert "Bad response code: 300" == e.value.msg s.expect("GET", "http://www.example.org/path/", params=dict(result="BAD")) with pytest.raises(BeanBagException) as e: GET(b(result="BAD")) assert "Could not decode response" == e.value.msg def test_sane_inheritance(): class MyBeanBag(BeanBag): def helper(self, param): pass def encode(self, body): return super(~MyBeanBag, self).encode(body) def decode(self, response): return super(~MyBeanBag, self).decode(response) s = FakeSession() bb = MyBeanBag("http://www.example.org/path", session=s) assert type(bb) is MyBeanBag assert type(bb.subpath) is MyBeanBag assert type(bb[1]) is MyBeanBag assert hasattr(~MyBeanBag, "helper") s.expect("GET", "http://www.example.org/path/") GET(bb) s.expect("POST", "http://www.example.org/path/foo", data={"a": 1}) r = POST(bb.foo, dict(a=1)) assert type(r) is AttrDict assert r.data == '{"a": 1}' and r.method == 'POST' beanbag-1.9.2/tests/test_attrdict.py0000644000175000017500000000134412502731370016503 0ustar ajaj00000000000000#!/usr/bin/env python import py.test from beanbag.attrdict import AttrDict import json j = json.loads('{"a": 1, "b": "blat", "c": {"d": "e"}}') ad = AttrDict(j) def test_manipulation(): assert ad.c.d == "e" assert repr(ad.foo.bar) == "" ad.foo.bar = "hello" ad.foo.bar += ", world" ad.foo.baz = "yaarrrr" assert +ad.foo == {"bar": "hello, world", "baz": "yaarrrr"} py.test.raises(KeyError, "str(ad.c.x.y.z)") ad.c.x.y.z = 3 assert ad.c.x.y.z == 3 del ad.foo.bar assert +ad.foo == {"baz": "yaarrrr"} print(+ad) del ad.foo del ad.c.x assert +ad == {"a": 1, "b": "blat", "c": {"d": "e"}} py.test.raises(KeyError, "del ad.bar.baz") assert +ad is j beanbag-1.9.2/LICENSE0000644000175000017500000000220612502777435013131 0ustar ajaj00000000000000Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. Copyright (c) 2015 Anthony Towns Written by Anthony Towns Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. beanbag-1.9.2/setup.py0000644000175000017500000000306612502731370013627 0ustar ajaj00000000000000#!/usr/bin/env python from setuptools import setup, find_packages import codecs, os.path, re here = os.path.abspath(os.path.dirname(__file__)) # Read the version number from a source file. # Why read it, and not import? # see https://groups.google.com/d/topic/pypa-dev/0PkjVpcxTzQ/discussion def find_version(*file_paths): # Open in Latin-1 so that we avoid encoding errors. # Use codecs.open for Python 2 compatibility with codecs.open(os.path.join(here, *file_paths), 'r', 'latin1') as f: version_file = f.read() # The version line must have the form # __version__ = 'ver' version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") with codecs.open('README.rst', encoding='utf-8') as f: long_description = f.read() setup( name = 'beanbag', version = find_version('beanbag', '__init__.py'), description = 'A helper module for accessing REST APIs', long_description = long_description, packages=find_packages(exclude=["contrib", "docs", "tests*"]), install_requires = ['requests >= 0.12.1'], url = 'https://github.com/ajtowns/beanbag', author = 'Anthony Towns', author_email = 'aj@erisian.com.au', license = 'MIT', classifiers = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'License :: OSI Approved :: MIT License', ], ) beanbag-1.9.2/PKG-INFO0000644000175000017500000000170012506516023013203 0ustar ajaj00000000000000Metadata-Version: 1.1 Name: beanbag Version: 1.9.2 Summary: A helper module for accessing REST APIs Home-page: https://github.com/ajtowns/beanbag Author: Anthony Towns Author-email: aj@erisian.com.au License: MIT Description: BeanBag ======= BeanBag is a simple module that lets you access REST APIs in an easy way. For example: >>> import beanbag >>> github = beanbag.BeanBag("https://api.github.com") >>> watchers = github.repos.ajtowns.beanbag.watchers() >>> for w in watchers: ... print(w["login"]) See `http://beanbag.readthedocs.org/` for more information. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License beanbag-1.9.2/debian/0002755000175000017500000000000012506516023013334 5ustar ajaj00000000000000beanbag-1.9.2/debian/copyright0000644000175000017500000000247212503211745015272 0ustar ajaj00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: beanbag Upstream-Contact: Anthony Towns Source: http://pypi.python.org/pypi/beanbag Files: * Copyright: 2014, Red Hat, Inc. and/or its affiliates. 2015, Anthony Towns License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. beanbag-1.9.2/debian/rules0000755000175000017500000000050012503211745014405 0ustar ajaj00000000000000#!/usr/bin/make -f export PYBUILD_NAME=beanbag export PYBUILD_TEST_PYTEST = 1 %: dh $@ --with python2,python3,sphinxdoc --buildsystem=pybuild override_dh_auto_build: dh_auto_build mkdir -p docs/_static docs/_templates PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator beanbag-1.9.2/debian/control0000644000175000017500000000302112506515306014734 0ustar ajaj00000000000000Source: beanbag Maintainer: Anthony Towns Section: python Priority: optional Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 9), dh-python, python3-all, python3-setuptools, python-docutils, python-sphinx (>= 1.1), python-pytest, python3-pytest, python-requests Standards-Version: 3.9.6 Vcs-Git: https://github.com/ajtowns/beanbag.git Homepage: https://pypi.python.org/pypi/beanbag Package: python-beanbag Architecture: all Depends: ${misc:Depends}, ${python:Depends} Suggests: python-beanbag-doc, python-requests-oauthlib Description: Helper module for accessing REST APIs BeanBag is a simple module that lets you access REST APIs in an easy way. See `http://beanbag.readthedocs.org/` for more information. . This package installs the library for Python 2. Package: python3-beanbag Architecture: all Depends: ${misc:Depends}, ${python3:Depends} Suggests: python-beanbag-doc, python3-requests-oauthlib Description: Helper module for accessing REST APIs BeanBag is a simple module that lets you access REST APIs in an easy way. See `http://beanbag.readthedocs.org/` for more information. . This package installs the library for Python 3. Package: python-beanbag-doc Architecture: all Section: doc Depends: ${misc:Depends}, ${sphinxdoc:Depends} Description: Documentation for Python BeanBag module BeanBag is a simple module that lets you access REST APIs in an easy way. See `http://beanbag.readthedocs.org/` for more information. . This package installs the module's documentation. beanbag-1.9.2/debian/compat0000644000175000017500000000000212503211745014530 0ustar ajaj000000000000009 beanbag-1.9.2/debian/source/0002755000175000017500000000000012506516023014634 5ustar ajaj00000000000000beanbag-1.9.2/debian/source/format0000644000175000017500000000001412503211745016040 0ustar ajaj000000000000003.0 (quilt) beanbag-1.9.2/debian/changelog0000644000175000017500000000023212505051650015200 0ustar ajaj00000000000000beanbag (1.9.1-1) unstable; urgency=low * Initial release (Closes: Bug#780840) -- Anthony Towns Wed, 18 Mar 2015 14:58:56 +1000 beanbag-1.9.2/debian/python-beanbag-doc.install0000644000175000017500000000017212503211745020363 0ustar ajaj00000000000000build/html /usr/share/doc/python-beanbag-doc examples /usr/share/doc/python-beanbag-doc beanbag-1.9.2/setup.cfg0000644000175000017500000000007312506516023013731 0ustar ajaj00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 beanbag-1.9.2/.gitignore0000644000175000017500000000006212502731370014076 0ustar ajaj00000000000000beanbag.egg-info/ *.pyc build/ dist/ docs/_build/ beanbag-1.9.2/examples/0002755000175000017500000000000012506516023013730 5ustar ajaj00000000000000beanbag-1.9.2/examples/github0000644000175000017500000000131512505227754015144 0ustar ajaj00000000000000#!/usr/bin/env python import beanbag.v1 as beanbag import os import requests sess = requests.Session() sess.auth = (os.environ["GITHUB_ACCT"], os.environ["GITHUB_PASS"]) github = beanbag.BeanBag("https://api.github.com/", session=sess) myuser = github.user() me = myuser["login"] repos = github.users[me].repos() repo = {} who = {} for r in repos: rn = r["name"] repo[rn] = github.repos[me][rn]() stars = github.repos[me][rn].stargazers() for s in stars: sn = s["login"] if sn not in who: who[sn] = set() who[sn].add(rn) for w in sorted(who): print("%s:" % (w,)) for rn in sorted(who[w]): print(" %s -- %s" % (rn, repo[rn]["description"])) beanbag-1.9.2/examples/twitter0000755000175000017500000000157712505236220015365 0ustar ajaj00000000000000#!/usr/bin/env python import beanbag import requests class OAuthDanceTwitter(beanbag.OAuth10aDance): req_token = "https://api.twitter.com/oauth/request_token" authorize = "https://api.twitter.com/oauth/authorize" acc_token = "https://api.twitter.com/oauth/access_token" client_key, client_secret = (None, None) user_key, user_secret = (None, None) oauthDance = OAuthDanceTwitter( client_key=client_key, client_secret=client_secret, user_key=user_key, user_secret=user_secret) oauthDance.obtain_creds() session = requests.Session() session.auth = oauthDance.oauth() twitter = beanbag.BeanBag("https://api.twitter.com/1.1/", ext=".json", session=session) myacct = twitter.account.settings() me = myacct["screen_name"] tweets = twitter.statuses.user_timeline(screen_name=me, count=7) for tweet in tweets: print(repr(tweet["text"]))