{ "info": { "author": "Yuri D'Elia", "author_email": "wavexx@thregr.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: No Input/Output (Daemon)", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", "Operating System :: POSIX", "Operating System :: Unix", "Programming Language :: JavaScript", "Programming Language :: Perl", "Programming Language :: PHP", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Topic :: Software Development", "Topic :: Software Development :: Interpreters", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "===============\npython ``bond``\n===============\n---------------------------------------------------\nAmbivalent bonds between Python and other languages\n---------------------------------------------------\n\n.. contents::\n\nThe Python module ``bond`` supports transparent remote/recursive evaluation\nbetween Python and another interpreter through automatic call serialization.\n\nIn poorer words, a ``bond`` lets you call functions in other languages as they\nwere normal Python functions. It *also* allows other languages to *call Python\nfunctions* as if they were native.\n\nRemote output is also transparently redirected locally, and since the\nevaluation is performed through a persistent co-process, you can actually spawn\ninterpreters on different hosts through \"ssh\" efficiently.\n\n``bond`` currently supports PHP, Perl, JavaScript (Node.js) and Python itself.\n\n\nOverview\n========\n\n.. code:: python3\n\n >>> # Let's bond with a PHP interpreter\n >>> from bond import make_bond\n >>> php = make_bond('PHP')\n >>> php.eval_block('echo \"Hello world!\\n\";')\n Hello world!\n\n >>> # Make an expensive split function using PHP's explode\n >>> split = php.callable('explode')\n >>> split(' ', \"Hello world splitted by PHP!\")\n [u'Hello', u'world', u'splitted', u'by', u'PHP!']\n\n >>> # Call Python from PHP\n >>> def call_me():\n ... print(\"Hi, this is Python talking!\")\n >>> php.export(call_me)\n >>> php.eval('call_me()')\n Hi, this is Python talking!\n\n >>> # Use some remote resources\n >>> remote_php = make_bond('PHP', 'ssh remote php')\n >>> remote_php.eval_block('function call_me() { echo \"Hi from \" . system(\"hostname\") . \"!\"; }')\n >>> remote_php.eval('call_me()')\n Hi from remote!\n\n >>> # Bridge two worlds!\n >>> perl = make_bond('Perl')\n >>> php.proxy('explode', perl)\n >>> # note: explode is now available to Perl, but still executes in PHP\n >>> perl.eval('explode(\"=\", \"Mind=blown!\")')\n [u'Mind', u'blown!']\n\n\nPractical examples\n==================\n\nIncremental code-base migration\n-------------------------------\n\nI originally needed ``bond`` for migrating a large PHP project to Python. With\n``bond`` you can rewrite a program incrementally, while still executing all the\nexisting code unchanged. You can start by rewriting just a single function in\nan empty shell, wrapping your existing code:\n\n.. code:: python3\n\n from bond import make_bond\n import sys\n\n php = make_bond('PHP')\n php.eval_block('include(\"my_original_program.php\");')\n\n def new_function(arg)\n # do something here\n pass\n\n php.export(new_function, 'function_to_be_replaced')\n php.call('main', sys.argv)\n\n\nMixing Python 2/3 code bases\n----------------------------\n\nYou can use ``bond`` to mix Python 2/3 code. Python <=> Python bonds\nautomatically use pickling as a protocol, which makes serialization almost\ninvisible.\n\nIn this scenario, you can start writing new code directly on Python 3, while\nusing Python 2 only for the libraries which are still missing.\n\nFor example, you can use ``Mechanize`` on Python 3 with minimal changes:\n\n.. code:: python3\n\n from bond import make_bond\n py2 = make_bond('Python', 'python2', trans_except=False)\n py2.eval_block('import mechanize; br = mechanize.Browser()')\n py2.call('br.open', 'http://www.example.com')\n title = py2.call('br.title')\n\n``eval_block`` is only being used as an example here to make it self-contained.\nA more reasonable solution for larger chunks of code is to split the source\ninto a distinct file that can be loaded at once in the remote interpreter:\n\n.. code:: python3\n\n from bond import make_bond\n py2 = make_bond('Python', 'python2', trans_except=False)\n py2.eval_block('import .mypython2lib')\n\nThis reduces the amount of clutter and keeps the distinction between new and\nlegacy code clear. You should also keep in mind that since the remote language\nis itself Python, expressions themselves (for whenever ``call`` is\ninsufficient) can be quoted just by using ``repr``.\n\nSimilarly, you can use ``bond`` to combine regular CPython and PyPy runtimes\n(all the required modules work as expected on PyPy):\n\n.. code:: python3\n\n from bond import make_bond\n pypy = make_bond('Python', 'pypy')\n\n\nRemote/parallel computation\n---------------------------\n\nYou can easily use ``bond`` to perform remote/parallel computation. Nobody\nstops you from having multiple interpreters at the same time: you can create\nmultiple bonds to setup a poor-man's distributed system with minimal effort:\n\n.. code:: python3\n\n # setup the workers\n from bond import make_bond\n hosts = ['host1', 'host2', 'host3']\n nodes = [make_bond('Python', 'ssh {} python'.format(host)) for host in hosts]\n\n # load our libraries first\n for node in nodes:\n node.eval_block('from library import *')\n\n # execute \"do_something\" remotely on each worker\n from threading import Thread\n threads = [Thread(target=lambda: node.call('do_something')) for node in nodes]\n for thread in threads: thread.start()\n\n # collect the results\n results = [thread.join() for thread in threads]\n\nDistributed producer/consumer schemes also come for free by proxying calls:\n\n.. code:: python3\n\n host1.eval_block(r'''def consumer(data):\n # do something with data\n pass\n ''')\n\n host2.eval_block(r'''def producer():\n while True:\n\t data = function()\n\t consumer(data)\n ''')\n\n host1.proxy('consumer', host2)\n host2.call('producer')\n\nIt's even more interesting if you realize that the producers/consumers don't\neven have to be written in the same language, and don't know that the call is\nactually being forwarded.\n\n``bond`` doesn't even need to be installed remotely: the required setup is\ninjected directly into a live interpreter. The wire protocol is simple enough\nthat any language supporting an interactive REPL can be called. In fact, `the\ndrivers themselves `_ are designed to\nbe used from any other language.\n\n\nPrivilege separation\n--------------------\n\nThere might be times when it makes sense to create an unprivileged context to\nperform potentially dangerous operations, such as decoding an uploaded file on\nwhich you have zero trust. A common approach would be to communicate with an\nunprivileged daemon built for the purpose, but it usually requires dedicated\neffort. Running such processes using ``bond`` instead is almost trivial:\n\n.. code:: python3\n\n # early in the setup phase of our daemon we create a bond using\n # passwordless sudo, changing to an unprivileged user\n py = make_bond('Python', 'sudo -u nobody python',\n\t\t trans_except=False, protocol='JSON')\n py.eval_block('from mylibrary import decode_file')\n\n # make decode_file() available as a normal function\n decode_file = py.callable('decode_file')\n\n # assuming decode_file() takes a file name which is at least readable by\n # the unprivileged user, we can just take it's return value\n data = decode_file(path)\n\nContrarily to other examples involving Python, here we actually restrict the\nserialization protocol to plain ``JSON``. Nothing changes from the caller (our)\nperspective, except that the bond now can't share with us anything beyond\ntrivial types. Python <=> Python bonds use ``pickle`` by default, which is not\nsensible here as ``pickle`` allows arbitrary Python structures and handlers to\nbe run (including bytecode itself).\n\nIf just running the context as another user is not enough, then setting up an\nLXC container doesn't add much complexity, since we can just use\n``lxc-execute`` to attach directly to the new instance's STDIO:\n\n.. code:: python3\n\n py = make_bond('Python', 'lxc-execute -n -f /path/to/python',\n\t\t trans_except=False, protocol='JSON')\n\nThis way an ephemeral container is started/destroyed automatically along with\nour daemon. The container itself can expose just a few shared/read-only\ndirectories, or nothing at all if the entire I/O is built on top of ``bond``.\n\n\nAPI\n===\n\nInitialization\n--------------\n\nA ``bond.Bond`` object is not normally constructed directly, but by using the\n``bond.make_bond()`` function:\n\n.. code:: python3\n\n import bond\n interpreter = bond.make_bond('language')\n\nThe first argument should be the desired language name (\"JavaScript\", \"PHP\",\n\"Perl\", \"Python\"). The list of supported languages can be fetched dynamically\nusing ``bond.list_drivers()``.\n\nYou can override the default interpreter command using the second argument,\nwhich allows to specify any shell command to be executed:\n\n.. code:: python3\n\n import bond\n py = bond.make_bond('Python', 'ssh remote python3')\n\nAn additional *list* of arguments to the interpreter can be provided using the\nthird argument, ``args``:\n\n.. code:: python3\n\n import bond\n py = bond.make_bond('Python', 'ssh remote python3', ['-E', '-OO'])\n\nThe *arguments*, contrarily to the command, are automatically quoted.\n\nSome command line arguments may be supplied automatically by the driver to\nforce an interactive shell; for example \"-i\" is supplied if Python is\nrequested. You can disable default arguments by using ``def_args=False``.\n\nThe following keyword arguments are supported:\n\n``cwd``:\n\n Working directory for the interpreter (defaults to current working\n directory).\n\n``env``:\n\n Environment for the interpreter (defaults to ``os.environ``).\n\n``def_args``:\n\n Enable (default) or suppress default, extra command-line arguments to the\n interpreter.\n\n``timeout``:\n\n Defines the timeout for the underlying communication protocol. Note that\n ``bond`` cannot distinguish between a slow call or noise generated while the\n interpreter is set up. Defaults to 60 seconds.\n\n``logfile``:\n\n Accepts a file handle which is used to log the entire communication with the\n underlying interpreter for debugging purposes.\n\n``trans_except``:\n\n Enables/disables \"transparent exceptions\". Exceptions are always first class,\n but when ``trans_except`` is enabled, the exception objects themselves will\n be forwarded across the bond. If ``trans_except`` is disabled (the default\n for all languages except Python), then local exceptions will always contain a\n string representation of the remote exception instead, which avoids\n serialization errors.\n\n``protocol``:\n\n Forces a specific serialization protocol to be chosen. It's automatically\n selected when not specified, and usually matches \"JSON\".\n\n\n``bond.Bond`` Methods\n---------------------\n\nThe resulting ``bond.Bond`` class has the following methods:\n\n``eval(code)``:\n\n Evaluate and return the value of a *single statement* of code in the\n interpreter.\n\n``eval_block(code)``:\n\n Execute a \"code\" block inside the top-level of the interpreter. Any construct\n which is legal by the current interpreter is allowed. Nothing is returned.\n\n``ref(code)``:\n\n Return a reference to an *single, unevaluated statement* of code, which can\n be later used in eval(), eval_block() or as an *immediate* argument to call().\n See `Quoted expressions`_.\n\n``close()``:\n\n Terminate the communication with the interpreter.\n\n``call(name, *args)``:\n\n Call a function \"name\" in the interpreter using the supplied list of\n arguments \\*args (apply \\*args to a callable statement defined by \"name\").\n The arguments are automatically converted to their other language's\n counterpart. The return value is captured and converted back to Python as\n well.\n\n``callable(name)``:\n\n Return a function that calls \"name\":\n\n .. code:: python\n\n explode = php.callable('explode')\n # Now you can call explode as a normal, local function\n explode(' ', 'Hello world')\n\n``export(func, name)``:\n\n Export a local function \"func\" so that can be called on the remote language\n as \"name\". If \"name\" is not specified, use the local function name directly.\n Note that \"func\" must be a function *reference*, not a function name.\n\n``proxy(name, other, remote)``:\n\n Export a function \"name\" from the current ``bond`` to \"other\", named as\n \"remote\". If \"remote\" is not provided, the same value as \"name\" is used.\n\n``interact()``:\n\n Start an interactive session with the underlying interpreter. By default, all\n input lines are executed with bond.eval_block(). If \"!\" is pre-pended,\n execute a single statement with bond.eval() and print it's return value. You\n can continue the statement on multiple lines by leaving a trailing \"\\\\\". Type\n Ctrl+C to abort a multi-line block without executing it.\n\n\nExceptions\n----------\n\nAll exceptions thrown by the ``bond`` module are of base type ``RuntimeError``\n<= ``BondException``.\n\n``BondException``:\n Thrown during initialization or unrecoverable errors.\n\n``TerminatedException``:\n Thrown when the bond exits unexpectedly.\n\n``SerializationException``:\n Thrown when an object/exception which is sent *or* received cannot be\n serialized by the current protocol. The ``side`` attribute can be either\n \"local\" (when attempting to *send*) or \"remote\" (when *receiving*). A\n ``SerializationException`` is not fatal.\n\n``RemoteException``:\n Thrown for uncaught remote exceptions. The \"data\" attribute contains either\n the error message (with ``trans_except=False``) or the remote exception\n itself (``trans_except=True``).\n\nBeware that both ``SerializationException`` (with ``side=\"remote\"``) and\n``RemoteException`` may actually be originating from uncaught *local*\nexceptions when an exported function is called. Pay attention to the error\ntext/data in these cases, as it will contain several nested exceptions.\n\n\nQuoted expressions\n------------------\n\n``bond`` has minimal support for quoted expressions, through the use of\n``Bond.ref()``. ``ref()`` returns a reference to a unevaluated statement that\ncan be fed back to ``eval()``, ``eval_block()``, or as an *immediate* (i.e.:\nnot nested) argument to ``call()``. References are bound to the interpreter\nthat created them.\n\n``ref()`` allows to \"call\" methods that take remote un-serializable arguments,\nsuch as file descriptors, without the use of a support function and/or eval:\n\n.. code:: python3\n\n pl = make_bond('Perl')\n pl.eval_block('open($fd, \">file.txt\");')\n fd = pl.ref('$fd')\n pl.call('syswrite', fd, \"Hello world!\")\n pl.call('close', fd)\n\nSince ``ref()`` objects cannot be nested, there are still cases where it might\nbe necessary to use a support function. To demonstrate, we rewrite the above\nexample without quoted expressions, while still allowing an argument (\"Hello\nworld!\") to be local:\n\n.. code:: python3\n\n pl = make_bond('Perl')\n pl.eval_block('open($fd, \">file.txt\");')\n pl.eval_block('sub syswrite_fd { syswrite($fd, shift()); };')\n pl.call('syswrite_fd', \"Hello world!\")\n pl.eval('close($fd)')\n\nOr more succinctly:\n\n.. code:: python3\n\n pl.call('sub { syswrite($fd, shift()); }', \"Hello world!\")\n\n\nLanguage support\n================\n\nPython\n------\n\nPython, as the identity language, has no restriction on data types. Everything\nis pickled on both sides, including exceptions.\n\n\nSerialization:\n\n* Performed locally and remotely using ``cPickle`` in Python 2 or `pickle\n `_ in Python 3.\n\n* Serialization exceptions on the remote side are of base type\n ``TypeError`` <= ``_BOND_SerializationException``.\n\n\nPython 2 / Python 3 / PyPy:\n\nYou can freely mix Python versions between hosts/interpreters (that is: you can\nrun Python 3 code from a Python 2 host and vice-versa). You'll need to disable\ntransparent exceptions between major versions though, as the exception\nhierarchy is different:\n\n.. code:: python3\n\n # assuming a python2.7 environment\n from bond import make_bond\n py = make_bond('Python', 'python3', trans_except=False)\n\n\nPHP\n---\n\nRequirements:\n\n* The PHP's >= 5.3 command line interpreter needs to be installed. On\n Debian/Ubuntu, the required package is ``php5-cli``.\n\nSerialization:\n\n* Performed remotely using ``JSON``. Implement the `JsonSerializable\n `_ interface to\n tweak which/how objects are encoded.\n\n* Serialization exceptions on the remote side are of base type\n ``_BOND_SerializationException``. The detailed results of the error can\n also be retrieved using `json_last_error\n `_.\n\nLimitations:\n\n* PHP <= 5.3 doesn't support the ``JsonSerializable`` interface, and thus lacks\n the ability of serializing arbitrary objects.\n\n* You cannot use ``call`` on a built-in function such as \"echo\". You have to\n use a real function instead, like \"print\". You can still call \"echo\" by using\n ``eval`` or ``eval_block``.\n\n* Unfortunately, you cannot catch \"fatal errors\" in PHP. If the evaluated code\n triggers a fatal error it will terminate the bond without appeal. A common\n example of such error can be attempting to use an undefined variable or\n function (which could happen while prototyping).\n\n* Due to the inability to override built-in functions, ``error_reporting()`` is\n not completely transparent and always returns 0. It shouldn't be used to\n control the display error level. Use ``_BOND_error_reporting()`` instead,\n which has the same usage/signature as the built-in function.\n\n\nPerl\n----\n\nPerl is a quirky language, due to its syntax. We assume here you're an\nexperienced Perl developer.\n\nRequirements:\n\n* Perl >= 5.14 is required, with the following modules:\n\n - ``JSON``\n - ``Data::Dump``\n - ``IO::String``\n\n On Debian/Ubuntu, the required packages are ``libjson-perl``\n ``libdata-dump-perl`` and ``libio-string-perl``.\n\nSerialization:\n\n* Performed remotely using ``JSON``. Implement the `TO_JSON\n `_ method on\n blessed references to tweak which/how objects are encoded.\n\n* Serialization exceptions on the remote side are generated by dying with a\n ``_BOND_SerializationException`` @ISA.\n\nGotchas:\n\n* By default, evaluation is forced in array context, as otherwise most of the\n built-ins working with arrays would return an useless scalar. Use the\n \"scalar\" keyword for the rare cases when you really need it to.\n\n* You can \"call\" any function-like statement, as long as the last argument is\n expected to be an argument list. This allows you to call builtins directly:\n\n .. code:: python3\n\n perl.call('map { $_ + 1 }', [1, 2, 3])\n\n* You can of course \"call\" a statement that returns any ``CODE``. Meaning that\n you can call references to functions as long as you dereference them first:\n\n .. code:: python3\n\n perl.call('&$fun_ref', ...)\n perl.call('&{ $any->{expression} }', ...)\n\n Likewise you can \"call\" objects methods directly:\n\n .. code:: python3\n\n perl.call('$object->method', ...)\n\n* ``eval_block`` introduces a new block. Variables declared as \"my\" will not be\n visible into a subsequent ``eval_block``. Use a fully qualified name or \"our\"\n to define variables that should persist across blocks:\n\n .. code:: python3\n\n perl.eval_block('our $variable = 1;')\n perl.eval_block('do_something_with($variable);')\n\n\nJavaScript\n----------\n\nJavaScript is supported through `Node.js `_.\n\nRequirements:\n\n* Node.js v0.6.12 and v0.10.29 have been tested. On Debian/Ubuntu, the required\n package is ``nodejs``.\n\nSerialization:\n\n* Performed remotely using ``JSON``. Implement the `toJSON\n `_\n property to tweak which/how objects are encoded.\n\n* Serialization exceptions on the remote side are of base type\n ``TypeError`` <= ``_BOND_SerializationException``.\n\nLimitations:\n\n* Currently the code expects an unix-like environment with ``/dev/stdin`` to\n perform synchronous I/O.\n\n* Since there's no distinction between \"plain\" objects (dictionaries) and any\n other object, almost everything will be silently serialized. Define a custom\n \"toJSON\" property on your \"real\" objects to control this behavior.\n\n* When executing a remote JavaScript bond with Node.js <= 0.6, you need to\n manually invoke the REPL, as follows:\n\n .. code:: python3\n\n js = make_bond('JavaScript',\n\t\t \"ssh remote node -e 'require\\(\\\\\\\"repl\\\\\\\"\\).start\\(\\)'\",\n\t\t def_args=False)\n\n When executing \"node\" locally, or when using Node.js >= 0.10, this is not\n required (the \"-i\" flag is automatically provided).\n\n\nCommon limitations\n------------------\n\n* Except for Python, only basic types (booleans, numbers, strings, lists/arrays\n and maps/dictionaries) can be transferred between the interpreters.\n\n* Serialization is performed locally using ``JSON``. Implement a custom\n `JSONEncoder `_\n to tweak which/how objects are encoded.\n\n* If an object that cannot be serialized reaches a \"call\", \"eval\", or even a\n non-local return such as an *error or exception*, it will generate a\n ``SerializationException`` on the local (Python) side.\n\n* Strings are *always* UTF-8 encoded.\n\n* References are implicitly broken as *objects are transferred by value*. This\n is obvious, as you're talking with a separate process, but it can easily be\n forgotten due to the blurring of the boundary.\n\n* Calling functions across the bridge is slow, also in Python, due to the\n serialization. But the execution speed of the functions themselves is *not\n affected*. This might be perfectly reasonable if there are only occasional\n calls between languages, and/or the calls themselves take a significant\n fraction of time.\n\n\nGeneral/support mailing list\n============================\n\nIf you are interested in announcements and development discussions about\n``bond``, you can subscribe to the `bond-devel` mailing list by sending an\nempty email to .\n\nYou can contact the main author directly at , though using\nthe general list is encouraged.\n\n\nAuthors and Copyright\n=====================\n\n`python-bond` can be found at\nhttp://www.thregr.org/~wavexx/software/python-bond/\n\n| \"python-bond\" is distributed under the GNU GPLv2+ license (see COPYING).\n| Copyright(c) 2014-2015 by wave++ \"Yuri D'Elia\" .\n\npython-bond's GIT repository is publicly accessible at::\n\n git://src.thregr.org/python-bond\n\nor at https://github.com/wavexx/python-bond\n\n\nLatest release notes\n====================\n\n* Performance/documentation tweaks.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://www.thregr.org/~wavexx/software/python-bond/", "keywords": "javascript php perl python", "license": "GPL2", "maintainer": null, "maintainer_email": null, "name": "python-bond", "package_url": "https://pypi.org/project/python-bond/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/python-bond/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://www.thregr.org/~wavexx/software/python-bond/" }, "release_url": "https://pypi.org/project/python-bond/1.4/", "requires_dist": null, "requires_python": null, "summary": "transparent remote/recursive evaluation between Python and other languages", "version": "1.4" }, "last_serial": 1567649, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "58b409a28381ae1197940d840eec2a72", "sha256": "ec96a09263d0713a72abca6b7a925e2921fc50217028eb64c49399f901ec98cf" }, "downloads": -1, "filename": "python-bond-0.1.tar.gz", "has_sig": false, "md5_digest": "58b409a28381ae1197940d840eec2a72", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21095, "upload_time": "2014-07-28T14:18:42", "url": "https://files.pythonhosted.org/packages/c0/f7/6978b534477e5936311ad7b88da3ccaef48715beae2ce23b79cdfa69158a/python-bond-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "ed9c74e1f88e9199a7db59028d4c42d7", "sha256": "83372fbdc17d8b415e64d808e5b4b432d8cc767fe3d49e70b48d0acb1202a364" }, "downloads": -1, "filename": "python-bond-0.2.tar.gz", "has_sig": false, "md5_digest": "ed9c74e1f88e9199a7db59028d4c42d7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24146, "upload_time": "2014-07-30T15:23:56", "url": "https://files.pythonhosted.org/packages/61/86/664a9119852f14633ed5a4c1c488c4c7c351df5376c7d7663540a7c186ef/python-bond-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "5a71a834eccaacfaf77b276e537945c4", "sha256": "62c8978d4cc1512eb9f9431f010443aca1b79c6eb7fc7529ccc52871c437b726" }, "downloads": -1, "filename": "python-bond-0.3.tar.gz", "has_sig": false, "md5_digest": "5a71a834eccaacfaf77b276e537945c4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30117, "upload_time": "2014-08-04T13:04:40", "url": "https://files.pythonhosted.org/packages/c4/6c/e4a23acc87a0713fe24141f7c9493e5ad8106a998c38bece73aef46bbb1a/python-bond-0.3.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "3b9314feb1ea9f259ffe39299234a13c", "sha256": "a867e1cfbecc4a3850766c3cef633d8e8395c3d75051e00c6c8c2ad96012cd92" }, "downloads": -1, "filename": "python-bond-0.4.tar.gz", "has_sig": false, "md5_digest": "3b9314feb1ea9f259ffe39299234a13c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33027, "upload_time": "2014-08-06T22:13:35", "url": "https://files.pythonhosted.org/packages/4c/b0/74f0db9d6906e5fd8c612542e13902c25ddd81da8c9be874cf0b083b52e5/python-bond-0.4.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "9ed89b3765d7b11606bf34b4e03cf6aa", "sha256": "fcd0a7421ff639c5ffd7f6eb5720fb45c7b32372cf1cead2fe8232190091f613" }, "downloads": -1, "filename": "python-bond-0.5.tar.gz", "has_sig": false, "md5_digest": "9ed89b3765d7b11606bf34b4e03cf6aa", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35331, "upload_time": "2014-08-11T20:26:52", "url": "https://files.pythonhosted.org/packages/d3/7b/333fdbe764cd88868ce5095e6adbb0077f2b69212a463dce3351b5fbefbf/python-bond-0.5.tar.gz" } ], "1.0": [ { "comment_text": "", "digests": { "md5": "1b1e1cc34a926cd09e57ff56889dbf18", "sha256": "5726dd5ec5daab94ba7fe46693440a6ef9ba93f2c699e8d54f708a517abc28bc" }, "downloads": -1, "filename": "python-bond-1.0.tar.gz", "has_sig": false, "md5_digest": "1b1e1cc34a926cd09e57ff56889dbf18", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 47982, "upload_time": "2014-08-19T21:07:18", "url": "https://files.pythonhosted.org/packages/46/5f/2e9baa5fa64d4e5be8dd137674d3006d31e3a4391382e6d6975d32918c66/python-bond-1.0.tar.gz" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "37e4e7c68d2b466b5be3a0e9d120b39a", "sha256": "71be1aff5dd11d3d705b60d24579472c30cd0c1976591d987a2cd13c452e9097" }, "downloads": -1, "filename": "python-bond-1.1.tar.gz", "has_sig": false, "md5_digest": "37e4e7c68d2b466b5be3a0e9d120b39a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 48753, "upload_time": "2014-08-20T19:41:40", "url": "https://files.pythonhosted.org/packages/5e/24/0990b01cff33e92f2ac257b0036516ef4ff2361dbf3dbd4a8201d0f0edf3/python-bond-1.1.tar.gz" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "e86f5c21d04f1844d872a3722a7bba6b", "sha256": "d14850755feb3cbb7445bd4ca98883738de0ccda251897ebd27f353a595c936b" }, "downloads": -1, "filename": "python-bond-1.2.tar.gz", "has_sig": false, "md5_digest": "e86f5c21d04f1844d872a3722a7bba6b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50299, "upload_time": "2014-08-21T17:41:56", "url": "https://files.pythonhosted.org/packages/55/02/9f2b900f5d32fd5e3fc685a37120254fa1e300e7ce97d876a0a42490d04b/python-bond-1.2.tar.gz" } ], "1.3": [ { "comment_text": "", "digests": { "md5": "e11586b4785a3b34a4f51904d8bba237", "sha256": "0f2fb498b6d4736c0db4c5a5ff3226a857e6c1fc1c6f63964f928f434efa2ec7" }, "downloads": -1, "filename": "python-bond-1.3.tar.gz", "has_sig": false, "md5_digest": "e11586b4785a3b34a4f51904d8bba237", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 52413, "upload_time": "2014-09-12T10:58:06", "url": "https://files.pythonhosted.org/packages/93/6b/a72d78d7391157bc74b8f018b8fb1e6e2f05e8d7280d097557c549e94aa9/python-bond-1.3.tar.gz" } ], "1.4": [ { "comment_text": "", "digests": { "md5": "837ca4c50ce7d518ee9e1a9cddea7af9", "sha256": "3a729265cba3e21533e573aa398584a5ca3a12f88a0cf9b24616780cacd87a0e" }, "downloads": -1, "filename": "python-bond-1.4.tar.gz", "has_sig": false, "md5_digest": "837ca4c50ce7d518ee9e1a9cddea7af9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 48072, "upload_time": "2015-05-27T15:39:24", "url": "https://files.pythonhosted.org/packages/f8/21/3fa181233e500a59549fd794030da3ca906df87c814e974efd05eded33d6/python-bond-1.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "837ca4c50ce7d518ee9e1a9cddea7af9", "sha256": "3a729265cba3e21533e573aa398584a5ca3a12f88a0cf9b24616780cacd87a0e" }, "downloads": -1, "filename": "python-bond-1.4.tar.gz", "has_sig": false, "md5_digest": "837ca4c50ce7d518ee9e1a9cddea7af9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 48072, "upload_time": "2015-05-27T15:39:24", "url": "https://files.pythonhosted.org/packages/f8/21/3fa181233e500a59549fd794030da3ca906df87c814e974efd05eded33d6/python-bond-1.4.tar.gz" } ] }