{ "info": { "author": "Noam Raph", "author_email": "noamraph@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "byteplay lets you convert Python code objects into equivalent objects which are easy to play with, and lets you convert those objects back into living Python code objects. It's useful for applying crazy transformations on Python functions, and is also useful in learning Python byte code intricacies. It currently works with Python 2.4 and up.\n\nbyteplay Module Documentation\n=============================\n\nAbout byteplay\n--------------\n\nbyteplay is a module which lets you easily play with Python bytecode. I wrote\nit because I needed to manipulate Python bytecode, but didn't find any suitable\ntool. Michael Hudson's bytecodehacks (http://bytecodehacks.sourceforge.net/)\ncould have worked fine for me, but it only works with Python 1.5.2. I also\nlooked at Phillip J. Eby's peak.util.assembler\n(http://pypi.python.org/pypi/BytecodeAssembler), but it's intended at\ncreating new code objects from scratch, not for manipulating existing code\nobjects.\n\nSo I wrote byteplay. The basic idea is simple: define a new type, named Code,\nwhich is equivalent to Python code objects, but, unlike Python code objects, is\neasy to play with. \"Equivalent\" means that every Python code object can be\nconverted to a Code object and vice-versa, without losing any important\ninformation on the way. \"Easy to play with\" means... well, exactly that. The\nrepresentation should be as simple as possible, letting the infrastructure sort\nout technical details which do not affect the final behaviour.\n\nIf you are interested in changing the behaviour of functions, or in assembling\nfunctions on your own, you may find byteplay useful. You may also find it useful\nif you are interested in how Python's bytecode actually works - byteplay lets\nyou easily play with existing bytecode and see what happens, which is a great\nway to learn. You are also welcome to check byteplay's (pure Python) code, to\nsee how it manipulates real bytecode.\n\nbyteplay can be downloaded from http://byteplay.googlecode.com/svn/trunk/byteplay.py . See http://code.google.com/p/byteplay/ for a bit more administrative info.\n\nFeel free to improve this document - that's why it's on the wiki! Also, if you find it useful, please drop me an email at noamraph at gmail dot com - it would be nice knowing that what I did was useful to someone...\n\nA Quick Example\n---------------\n\nLet's start from a quick example, to give you a taste of what byteplay does.\nLet's define this stupid function::\n\n >>> def f(a, b):\n ... print (a, b)\n ...\n >>> f(3, 5)\n (3, 5)\n\nNow, let's use byteplay to see what its bytecode is actually doing::\n\n >>> from byteplay import *\n >>> from pprint import pprint\n >>> c = Code.from_code(f.func_code)\n >>> pprint(c.code)\n [(SetLineno, 2),\n (LOAD_FAST, 'a'),\n (LOAD_FAST, 'b'),\n (BUILD_TUPLE, 2),\n (PRINT_ITEM, None),\n (PRINT_NEWLINE, None),\n (LOAD_CONST, None),\n (RETURN_VALUE, None)]\n\nI hope that this is pretty clear if you are a bit familiar with bytecode. The\nCode object contains a list of all operations, which are pairs of (opcode,\narg). Not all opcodes have an argument, so they have None as their argument. You\ncan see that no external tables are used: in the raw bytecode, the argument of\nmany opcodes is an index to a table - for example, the argument of the\nLOAD_CONST opcode is an index to the co_consts table, which contains the actual\nconstants. Here, the argument is the constant itself. Also note the SetLineno\n\"opcode\". It is not a real opcode, but it is used to declare where a line in the\noriginal source code begins. Besides another special opcode defined by byteplay,\nwhich we will see later, all other opcodes are the real opcodes used by the\nPython interpreter.\n\nBy the way, if you want to see the code list in a form which is\neasier to read, you can simply print it, like this::\n\n >>> print c.code\n\n 2 1 LOAD_FAST a\n 2 LOAD_FAST b\n 3 BUILD_TUPLE 2\n 4 PRINT_ITEM\n 5 PRINT_NEWLINE\n 6 LOAD_CONST None\n 7 RETURN_VALUE\n\nThis is especially useful if the code contains jumps. See the\ndescription of the printcodelist function for another example.\n\nOk, now let's play! Say we want to change the function, to print its arguments\nin reverse order. To do this, we will add a ROT_TWO opcode after the two\narguments were loaded to the stack. See how simple it is::\n\n >>> c.code[3:3] = [(ROT_TWO, None)]\n >>> f.func_code = c.to_code()\n >>> f(3, 5)\n (5, 3)\n\n\nOpcodes\n-------\n\nWe have seen that the code list contains opcode constants such as LOAD_FAST.\nThese are instances of the Opcode class. The Opcode class is a subclass of int,\nwhich overrides the ``__repr__`` method to return the string representation of\nan opcode. This means that instead of using a constant such as LOAD_FAST, a\nnumerical constant such as 124 can be used. Opcode instances are, of course,\nmuch easier to understand. The byteplay module creates Opcode instances for all\nthe interpreter opcodes. They can be found in the ``opcodes`` set, and also in\nthe module's global namespace, so you can write ``from byteplay import *`` and\nuse the opcode constants immediately.\n\nbyteplay doesn't include a constant for the EXTENDED_ARG opcode, as it is not\nused by byteplay's representation.\n\n\nModule Contents\n---------------\n\nThese are byteplay's public attributes, which are imported when ``from byteplay\nimport *`` is done.\n\n``POP_TOP``, ``ROT_TWO``, etc.\n All bytecode constants are imported by their names.\n\n``opcodes``\n A set of all Opcode instances.\n\n``opmap``\n A mapping from an opcode name to an Opcode instance.\n\n``opname``\n A mapping from an opcode number (and an Opcode instance) to its name.\n\n``cmp_op``\n A list of strings which represent comparison operators. In raw bytecode, the\n argument of the COMPARE_OP opcode is an index to this list. In the code list,\n it is the string representing the comparison.\n\nThe following are sets of opcodes, which list opcodes according to their\nbehaviour.\n\n``hasarg``\n This set contains all opcodes which have an argument (these are the opcodes\n which are >= HAVE_ARGUMENT).\n\n``hasname``\n This set contains all opcodes whose argument is an index to the co_names list.\n\n``hasjrel``\n This set contains all opcodes whose argument is a relative jump, that is, an\n offset by which to advance the byte code instruction pointer.\n\n``hasjabs``\n This set contains all opcodes whose argument is an absolute jump, that is, an\n address to which the instruction pointer should jump.\n\n``hasjump``\n This set contains all opcodes whose argument is a jump. It is simply\n ``hasjrel + hasjabs``. In byteplay, relative and absolute jumps behave in the\n same way, so this set is convenient.\n\n``haslocal``\n This set contains all opcodes which operate on local variables.\n\n``hascompare``\n This set contains all opcodes whose argument is a comparison operator - that\n is, only the COMPARE_OP opcode.\n\n``hasfree``\n This set contains all opcodes which operate on the cell and free variable\n storage. These are variables which are also used by an enclosing or an\n enclosed function.\n\n``hascode``\n This set contains all opcodes which expect a code object to be at the top of\n the stack. In the bytecode the Python compiler generates, they are always\n preceded by a LOAD_CONST opcode, which loads the code object.\n\n``hasflow``\n This set contains all opcodes which have a special flow behaviour. All other\n opcodes always continue to the next opcode after finished, unless an exception\n was raised.\n\nThe following are the types of the first elements of the opcode list tuples.\n\n``Opcode``\n The type of all opcode constants.\n\n``SetLineno``\n This singleton is used like the \"real\" opcode constants, but only declares\n where starts the bytecode for a specific line in the source code.\n\n``Label``\n This is the type of label objects. This class does nothing - it is used as a\n way to refer to a place in the code list.\n\nHere come some additional functions.\n\n``isopcode(obj)``\n Use this function to check whether the first element of an operation pair is\n a real opcode. This simply returns ``obj is not SetLineno and not\n isinstance(obj, Label)``.\n\n``getse(op[, arg])``\n This function gets the stack effect of an opcode, as a (pop, push) tuple. The\n stack effect is the number of items popped from the stack, and the number of\n items pushed instead of them. If an item is only inspected, it is considered\n as though it was popped and pushed again. This function is meaningful only\n for opcodes not in hasflow - for other opcodes, ValueError will be raised.\n\n For some opcodes the argument is needed in order to calculate the stack\n effect. In that case, if arg isn't given, ValueError will be raised.\n\n``printcodelist(code, to=sys.stdout)``\n This function gets a code list and prints it in a way easier to read. For\n example, let's define a simple function::\n\n >>> def f(a):\n ... if a < 3:\n ... b = a\n ...\n >>> c = Code.from_code(f.func_code)\n\n This is the code list itself::\n\n >>> pprint(c.code)\n [(SetLineno, 2),\n (LOAD_FAST, 'a'),\n (LOAD_CONST, 3),\n (COMPARE_OP, '<'),\n (JUMP_IF_FALSE, ),\n (POP_TOP, None),\n (SetLineno, 3),\n (LOAD_FAST, 'a'),\n (STORE_FAST, 'b'),\n (JUMP_FORWARD, ),\n (, None),\n (POP_TOP, None),\n (, None),\n (LOAD_CONST, None),\n (RETURN_VALUE, None)]\n\n And this is the nicer representation::\n\n >>> printcodelist(c.code)\n\n 2 1 LOAD_FAST a\n 2 LOAD_CONST 3\n 3 COMPARE_OP <\n 4 JUMP_IF_FALSE to 11\n 5 POP_TOP\n\n 3 7 LOAD_FAST a\n 8 STORE_FAST b\n 9 JUMP_FORWARD to 13\n >> 11 POP_TOP\n >> 13 LOAD_CONST None\n 14 RETURN_VALUE\n\n As you can see, all opcodes are marked by their index in the list,\n and jumps show the index of the target opcode.\n\nFor your convenience, another class was defined:\n\n``CodeList``\n This class is a list subclass, which only overrides the __str__ method to\n use ``printcodelist``. If the code list is an instance of CodeList, you don't\n have to type ``printcodelist(c.code)`` in order to see the nice\n representation - just type ``print c.code``. Code instances created from\n raw Python code objects already have that feature!\n\nAnd, last but not least - the Code class itself!\n\n\nThe Code Class\n--------------\n\nConstructor\n~~~~~~~~~~~\n\n::\n\n Code(code, freevars, args, varargs, varkwargs, newlocals,\n name, filename, firstlineno, docstring) -> new Code object\n\nThis constructs a new Code object. The argument are simply values for the\nCode object data attributes - see below.\n\nData Attributes\n~~~~~~~~~~~~~~~\n\nWe'll start with the data attributes - those are read/write, and distinguish\none code instance from another. First come the attributes which affect the\noperation of the interpreter when it executes the code, and then come attributes\nwhich give extra information, useful for debugging and introspection.\n\n``code``\n This is the main part which describes what a Code object does. It is a list\n of pairs ``(opcode, arg)``. ``arg`` is the opcode argument, if it has one, or\n None if it doesn't. ``opcode`` can be of 3 types:\n\n * Regular opcodes. These are the opcodes which really define an operation of\n the interpreter. They can be regular ints, or Opcode instances. The\n meaning of the argument changes according to the opcode:\n\n - Opcodes not in ``hasarg`` don't have an argument. None should be used as\n the second item of the tuple.\n - The argument of opcodes in ``hasconst`` is the actual constant.\n - The argument of opcodes in ``hasname`` is the name, as a string.\n - The argument of opcodes in ``hasjump`` is a Label instance, which should\n point to a specific location in the code list.\n - The argument of opcodes in ``haslocal`` is the local variable name, as\n a string.\n - The argument of opcodes in ``hascompare`` is the string representing the\n comparison operator.\n - The argument of opcodes in ``hasfree`` is the name of the cell or free\n variable, as a string.\n - The argument of the remaining opcodes is the numerical argument found in\n raw bytecode. Its meaning is opcode specific.\n\n * ``SetLineno``. This is a singleton, which means that a line in the source\n code begins. Its argument is the line number.\n\n * labels. These are instances of the ``Label`` class. The label class does\n nothing - it is just used as a way to specify a place in the code list.\n Labels can be put in the code list and cause no action by themselves.\n They are used as the argument of opcodes which may cause a jump to a\n specific location in the code.\n\n``freevars``\n This is a list of strings - the names of variables defined in outer functions\n and used in this function or in functions defined inside it. The order of this\n list is important, since those variables are passed to the function as a\n sequence whose order should match the order of the ``freevars`` attribute.\n\n A few words about closures in Python may be in place. In Python, functions\n defined inside other functions can use variables defined in an outer function.\n We know each running function has a place to store local variables. But how\n can functions refer to variables defined in an outer scope?\n\n The solution is this: for every variable which is used in more than one scope,\n a new ``cell`` object is created. This object does one simple thing: it refers\n to one another object - the value of its variable. When the variable gets a\n new value, the cell object is updated too. A reference to the cell object is\n passed to any function which uses that variable. When an inner function is\n interested in the value of a variable of an outer scope, it uses the value\n referred by the cell object passed to it.\n\n An example might help understand this. Let's take a look at the bytecode of a\n simple example::\n\n >>> def f():\n ... a = 3\n ... b = 5\n ... def g():\n ... return a + b\n ...\n >>> from byteplay import *\n >>> c = Code.from_code(f.func_code)\n >>> print c.code\n\n 2 1 LOAD_CONST 3\n 2 STORE_DEREF a\n\n 3 4 LOAD_CONST 5\n 5 STORE_DEREF b\n\n 4 7 LOAD_CLOSURE a\n 8 LOAD_CLOSURE b\n 9 BUILD_TUPLE 2\n 10 LOAD_CONST \n 11 MAKE_CLOSURE 0\n 12 STORE_FAST g\n 13 LOAD_CONST None\n 14 RETURN_VALUE\n\n >>> c.code[10][1].freevars\n ('a', 'b')\n >>> print c.code[10][1].code\n\n 5 1 LOAD_DEREF a\n 2 LOAD_DEREF b\n 3 BINARY_ADD\n 4 RETURN_VALUE\n\n We can see that LOAD_DEREF and STORE_DEREF opcodes are used to get and set the\n value of cell objects. There is no inherent difference between cell objects\n created by an outer function and cell objects used in an inner function. What\n makes the difference is whether a variable name was listed in the ``freevars``\n attribute of the Code object - if it was not listed there, a new cell is\n created, and if it was listed there, the cell created by an outer function\n is used.\n\n We can also see how a function gets the cell objects it needs from its outer\n functions. The inner function is created with the MAKE_CLOSURE opcode, which\n pops two objects from the stack: first, the code object used to create the\n function. Second, a tuple with the cell objects used by the code (the tuple\n is created by the LOAD_CLOSURE opcodes, which push a cell object into the\n stack, and of course the BUILD_TUPLE opcode.) We can see that the order of the\n cells in the tuple match the order of the names in the ``freevars`` list -\n that's how the inner function knows that ``(LOAD_DEREF, 'a')`` means \"load\n the value of the first cell in the tuple\".\n\n``args``\n The list of arguments names of a function. For example::\n\n >>> def f(a, b, *args, **kwargs):\n ... pass\n ...\n >>> Code.from_code(f.func_code).args\n ('a', 'b', 'args', 'kwargs')\n\n``varargs``\n A boolean: Does the function get a variable number of positional arguments?\n In other words: does it have a ``*args`` argument?\n\n If ``varargs`` is True, the argument which gets that extra positional\n arguments will be the last argument or the one before the last, depending\n on whether ``varkwargs`` is True.\n\n``varkwargs``\n A boolean: Does the function get a variable number of keyword arguments?\n In other words: does it have a ``**kwargs`` argument?\n\n If ``varkwargs`` is True, the argument which gets the extra keyword arguments\n will be the last argument.\n\n``newlocals``\n A boolean: Should a new local namespace be created for this code? This\n is True for functions and False for modules and exec code.\n\nNow come attributes with additional information about the code:\n\n``name``\n A string: The name of the code, which is usually the name of the function\n created from it.\n\n``filename``\n A string: The name of the source file from which the bytecode was compiled.\n\n``firstlineno``\n An int: The number of the first line in the source file from which the\n bytecode was compiled.\n\n``docstring``\n A string: The docstring for functions created from this code.\n\n\nMethods\n~~~~~~~\n\nThese are the Code class methods.\n\n``Code.from_code(code) -> new Code object``\n This is a static method, which creates a new Code object from a raw Python\n code object. It is equivalent to the raw code object, that is, the resulting\n Code object can be converted to a new raw Python code object, which will\n have exactly the same behaviour as the original object.\n\n``code.to_code() -> new code object``\n This method converts a Code object into an equivalent raw Python code object,\n which can be executed just like any other code object.\n\n``code1.__eq__(code2) -> bool``\n Different Code objects can be meaningfully tested for equality. This tests\n that all attributes have the same value. For the code attribute, labels are\n compared to see if they form the same flow graph.\n\n\nStack-depth Calculation\n-----------------------\n\nWhat was described above is enough for using byteplay. However, if you encounter\nan \"Inconsistent code\" exception when you try to assemble your code and wonder\nwhat it means, or if you just want to learn more about Python's stack\nbehaviour, this section is for you.\n\nNote: This section isn't as clear as it could have been, to say the least. If\nyou like to improve it, feel free to do so - that's what wikis are for, aren't\nthey?\n\nWhen assembling code objects, the code's maximum stack usage is needed. This is\nsimply the maximum number of items expected on the frame's stack. If the actual\nnumber of items in stack exceeds this, Python may well fail with a segmentation\nfault. The question is then, how to calculate the maximum stack usage of a\ngiven code?\n\nThere's most likely no general solution for this problem. However, code\ngenerated by Python's compiler has a nice property which makes it relatively\neasy to calculate the maximum stack usage. The property is that if we take a\nbytecode \"line\", and check the stack state whenever we reach that line, we will\nfind the stack state when we reach that line is always the same, no matter how\nwe got to that line. We'll call such code \"regular\".\n\nNow, this requires clarification: what is the \"stack state\" which is always the\nsame, exactly? Obviously, the stack doesn't always contain the same objects\nwhen we reach a line. For now, we can assume that it simply means the number of\nitems on the stack.\n\nThis helps us a lot. If we know that every line can have exactly one stack\nstate, and we know how every opcode changes the stack state, we can trace stack\nstates along all possible code paths, and find the stack state of every\nreachable line. Then we can simply check what state had the largest number of\nstack items, and that's the maximum stack usage of the code. What will happen\nwith code not generated by Python's compiler, if it doesn't fulfill the\nrequirement that every line should have one state? When tracing the stack state\nfor every line, we will find a line, which can be reached from several places,\nwhose stack state changes according to the address from which we jumped to that\nline. In that case, An \"Inconsistent code\" exception will be raised.\n\nOk, what is really what we called \"stack state\"? If every opcode pushed and\npopped a constant number of elements, the stack state could have been the\nnumber of items on stack. However, life isn't that simple. In real life, there\nare *blocks*. Blocks allow us to break from a loop, regardless of exactly how\nmany items we have in stack. How? Simple. Before the loop starts, the\nSETUP_LOOP opcode is executed. This opcode records in a block the number of operands(items)\ncurrently in stack, and also a position in the code. When the POP_BLOCK is executed, the stack is restored to the recorded state by poping extra items, and the corresponding block is\ndiscarded. But if the BREAK_LOOP opcode is\nexecuted instead of POP_BLOCK, one more thing happens. The\nexecution jumps to the position specified by the SETUP_LOOP opcode.\n\nFortunately, we can still live with that. Instead of defining the stack state\nas a single number - the total number of elements in the stack, we will define\nthe stack state as a sequence of numbers - the number of elements in the stack\nper each block. So, for example, if the state was (3, 5), after a BINARY_ADD\noperation the state will be (3, 4), because the operation pops two elements and\npushes one element. If the state was (3, 5), after a PUSH_BLOCK operation the\nstate will be (3, 5, 0), because a new block, without elements yet, was pushed.\n\nAnother complication: the SETUP_FINALLY opcode specifies an address to jump to\nif an exception is raised or a BREAK_LOOP operation was executed. This address\ncan also be reached by normal flow. However, the stack state in that address\nwill be different, depending on what actually happened - if an exception was\nraised, 3 elements will be pushed, if BREAK_LOOP was executed, 2 elements will\nbe pushed, and if nothing happened, 1 element will be pushed by a LOAD_CONST\noperation. This seemingly non-consistent state always ends with an\nEND_FINALLY opcode. The END_FINALLY opcodes pops 1, 2 or 3 elements according to\nwhat it finds on stack, so we return to \"consistent\" state. How can we deal\nwith that complexity?\n\nThe solution is pretty simple. We will treat the SETUP_FINALLY opcode as\nif it pushes 1 element to its target - this makes it consistent with the 1\nelement which is pushed if the target is reached by normal flow. However,\nwe will calculate the stack state as if at the target line there was an opcode\nwhich pushed 2 elements to the stack. This is done so that the maximum stack\nsize calculation will be correct. Those 2 extra elements will be popped by the\nEND_FINALLY opcode, which will be treated as though it always pops 3 elements.\nThat's all! Just be aware of that when you are playing with\nSETUP_FINALLY and END_FINALLY opcodes...", "description_content_type": null, "docs_url": null, "download_url": "http://code.google.com/p/byteplay/downloads/list", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://code.google.com/p/byteplay", "keywords": null, "license": "LGPL", "maintainer": null, "maintainer_email": null, "name": "byteplay", "package_url": "https://pypi.org/project/byteplay/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/byteplay/", "project_urls": { "Download": "http://code.google.com/p/byteplay/downloads/list", "Homepage": "http://code.google.com/p/byteplay" }, "release_url": "https://pypi.org/project/byteplay/0.2/", "requires_dist": null, "requires_python": null, "summary": "bytecode manipulation library", "version": "0.2" }, "last_serial": 233423, "releases": { "0.1": [], "0.2": [ { "comment_text": "", "digests": { "md5": "ba06da6bb3c584b942ff4787732f6cf0", "sha256": "21e77bf2e09289c9e31e488b2be9561cedfb8ca37ab0668df0092e627750d7dd" }, "downloads": -1, "filename": "byteplay-0.2-py2.6.egg", "has_sig": false, "md5_digest": "ba06da6bb3c584b942ff4787732f6cf0", "packagetype": "bdist_egg", "python_version": "2.6", "requires_python": null, "size": 30291, "upload_time": "2010-09-14T21:22:54", "url": "https://files.pythonhosted.org/packages/20/3a/4ed454c8992acf479df8ae86207b56a0bb9f83f743f12a2c955cdb317aaf/byteplay-0.2-py2.6.egg" }, { "comment_text": "", "digests": { "md5": "2c67f1fe9d33baa274aedcf1a621f569", "sha256": "fe4420e416c036c72ea5a20dfee2aabe06e2bf297b7ed65fcba770955a38fa3b" }, "downloads": -1, "filename": "byteplay-0.2-py2.7.egg", "has_sig": false, "md5_digest": "2c67f1fe9d33baa274aedcf1a621f569", "packagetype": "bdist_egg", "python_version": "2.7", "requires_python": null, "size": 30200, "upload_time": "2010-09-19T23:14:14", "url": "https://files.pythonhosted.org/packages/e6/11/5a46f6e377039cab8a3413421d1ac801aea0731821aa2027105921f6cc25/byteplay-0.2-py2.7.egg" }, { "comment_text": "", "digests": { "md5": "e64e467c43be61e0ed49d13a50754735", "sha256": "f1feededb3cdfd75e0deba963788e218fe2a9e0f7f769cb24ebb0a6746a746b8" }, "downloads": -1, "filename": "byteplay-0.2.tar.gz", "has_sig": false, "md5_digest": "e64e467c43be61e0ed49d13a50754735", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30596, "upload_time": "2010-09-14T21:22:56", "url": "https://files.pythonhosted.org/packages/38/27/cfa614cf50feb4676f08d0990457754491a732d8662d2db0bab750c6a716/byteplay-0.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ba06da6bb3c584b942ff4787732f6cf0", "sha256": "21e77bf2e09289c9e31e488b2be9561cedfb8ca37ab0668df0092e627750d7dd" }, "downloads": -1, "filename": "byteplay-0.2-py2.6.egg", "has_sig": false, "md5_digest": "ba06da6bb3c584b942ff4787732f6cf0", "packagetype": "bdist_egg", "python_version": "2.6", "requires_python": null, "size": 30291, "upload_time": "2010-09-14T21:22:54", "url": "https://files.pythonhosted.org/packages/20/3a/4ed454c8992acf479df8ae86207b56a0bb9f83f743f12a2c955cdb317aaf/byteplay-0.2-py2.6.egg" }, { "comment_text": "", "digests": { "md5": "2c67f1fe9d33baa274aedcf1a621f569", "sha256": "fe4420e416c036c72ea5a20dfee2aabe06e2bf297b7ed65fcba770955a38fa3b" }, "downloads": -1, "filename": "byteplay-0.2-py2.7.egg", "has_sig": false, "md5_digest": "2c67f1fe9d33baa274aedcf1a621f569", "packagetype": "bdist_egg", "python_version": "2.7", "requires_python": null, "size": 30200, "upload_time": "2010-09-19T23:14:14", "url": "https://files.pythonhosted.org/packages/e6/11/5a46f6e377039cab8a3413421d1ac801aea0731821aa2027105921f6cc25/byteplay-0.2-py2.7.egg" }, { "comment_text": "", "digests": { "md5": "e64e467c43be61e0ed49d13a50754735", "sha256": "f1feededb3cdfd75e0deba963788e218fe2a9e0f7f769cb24ebb0a6746a746b8" }, "downloads": -1, "filename": "byteplay-0.2.tar.gz", "has_sig": false, "md5_digest": "e64e467c43be61e0ed49d13a50754735", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30596, "upload_time": "2010-09-14T21:22:56", "url": "https://files.pythonhosted.org/packages/38/27/cfa614cf50feb4676f08d0990457754491a732d8662d2db0bab750c6a716/byteplay-0.2.tar.gz" } ] }