{ "info": { "author": "Edward Newell", "author_email": "edward.newell@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Build Tools" ], "description": "# Cluster-func\n\nCluster-func is a command line tool that lets you scale embarassingly parallel \nsolution written in Python with zero additional code.\n\n## Install\n\nWith pip\n```bash\npip install cluster-func\n```\n\nFrom source\n```bash\ngit clone git://github.com/enewel101/cluster-func\ncd cluster-func\npython setup.py develop\n```\n\n## Why?\n\nToo often I find myself writing multiprocessing and job-division / scheduling\nboilerplate code, which, despite being conceptually straightforward, is tedious\nand error-prone.\n\nSure, `xargs` is nice, assuming that you can conveniently get the shell to\nspit out your argument values. But that's often not the case, and \nyou may want your arguments to be arbitrary python types instead of strings.\n\nAnd, sure, Hadoop is nice too, assuming you've got a lot of time to burn\nconfiguring it, and that your mappers and reducers don't use too much \nmemory, and that you've loaded all your data in the hdfs, \nand that the results from your maps don't \nyield too much data and overwhelm the network. Oh, and assuming\nyou enjoy writing boilerplate mapper and reducer code... wait, maybe hadoop\nisn't so nice... (OK, OK, it does have its place!)\n\n## Basic usage\nCluster-func is designed for situations where you need to run a single function\non many different arguments. This kind embarassingly parallelizable problem\ncomes up pretty often. At it's minimum, a solution involves defining\n**A)** the function to be repeatedly called, and **B)** all of the different \narguments on which it should be called. \n\nCluster-func assumes that you have defined\nthese in the form of a *callable* and an *iterable* within a python script, and\nthen it handles the business of spreading the work across cores and machines.\n\nThe nice thing about this approach is that you unavoidably define these two\nthings when you write your code for a single process anyway. So you'll get\nmultiprocessing and cluster processing basically for free!\n\nThe tool has two modes. In **direct mode**, it runs your function on the cpus of\na single machine. In **dispatch mode**, it breaks the work into subjobs that can\nbe run on separate machines, and optionally submits them to a job scheduler\nusing `qsub`.\n\n### Direct mode\nThis command:\n```bash\n$ cluf my_script.py \n``` \nwill look in `my_script.py` for a function called `target` and an iterable called\n`args`, and it will run `target` on every single value yielded from `args`,\nspawning as many workers as there are cpu cores available to your user on the\nmachine. \n\nTo use other names for you target function or arguments iterable,\nor to use a different number of worker processes, do this:\n```bash\n$ cluf my_script.py --target=my_func --args=my_iterable --processes=12\t# short options -t, -a, -p\n```\n\nIf `args` yields a tuple, its contents will be unpacked and interpreted as the\npositional arguments for one invocation of the target function. If you need\ngreater control, for example, to provide keyword arguments, then see \n**Arguments iterable**\n\n`args` can also be a callable that *returns* an iterable (including a generator),\nwhich is often more convenient.\n\nSo, using `cluf` in direct mode lets you multiprocess any script without pools\nor queues or even importing multiprocessing. But, if you really need to scale\nup, then you'll want to use the dispatch mode.\n\n### Dispatch mode\nThe main use of dispatch mode is to spread the work in a cluster that\nuses the `qsub` command for job submission. But you can still use `cluf` to \nspread work between machines don't use `qsub`.\n\nDispatch mode is implicitly invoked when you specify a number of compute nodes\nto use (you can force the running mode using `--mode`, see **Reference**).\nFor example, this command:\n```bash\n$ cluf my_script.py --nodes=10 --queue\t# short options -n, -q\n```\nwill break the work into 10 subjobs, and submit them using qsub. It does\nthis by writing small shell scripts, each of which is responsible for calling \nthe target function on a subset of the arguments yielded by the iterable.\n\n(To learn about setting PBS directives for your subjob scripts, see **PBS\noptions** below.)\n\nBecause each subjob script is a valid shell script, you can manually run them\non separate machines in case they aren't part of a cluster that uses `qsub`.\nJust leave off the `--queue` option, and the scripts will be created but not\nenqueued. This is also a good way to test run one of the subjobs\nbefore submitting it.\n\nTo divide the work properly, it's important that your argument iterable yields\nthe same arguments in the same order on each machine. If you can't or don't\nwant write your iterable that way, see **How work is divided** for other options.\n\nAnd that's the end of the basic usage guide. This will cover you for the most \nbasic usecases. To learn about more cool features, read on!\n\n## Arguments iterable\nThe main usecase imagined is one where the arguments iterable yields either\nsingle, bare arguments, or tuples of positional arguments. Of course, the \nPython language provides a very\nflexible way of calling functions, allowing you to mix positional arguments\nand keyword arguments. If you need that flexibility, then set up your iterator\nto yield `cluster_func.Arguments` objects. This class acts as a proxy, and \nwill be used to call your target function in exactly the way you called the \n`Arguments` constructor.\n\nHere is the `Arguments` class in action:\n```python\n>>> from cluster_func import Arguments\n>>> my_args = Arguments(0, *[1,2,3], four=4, **{'five':5, 'six':6})\n>>> my_args\nArguments(0, 1, 2, 3, four=4, five=5, six=6)\n>>>\n>>> # Your target function would be called the same way you called Arguments, e.g.:\n>>> my_target(0, *[1,2,3], four=4, **{'five':5, 'six':6})\n```\n\n## How work is divided\nBy default, work is divided by assuming that the arguments iterator will yield\nthe same arguments in the same order during each subjob. Each subjob can then\nexecute the target function only on those arguments assigned to it. \n\nFor example, if there were 10 subjobs, subjob 7 would run arguments 7, 17, 27,\n... etc. For ease of explanation, we'll call that subset \"bin 7\".\n\nIf you open the subjob scripts in an editor, you'll find that they actually\ncall `cluf` itself in *direct mode*. In other words, when you run\n`cluf` in dispatch mode, it creates scripts that call `cluf` in direct mode.\n\nThese direct-mode invocations use the `--bin` option, which\nis what instructs `cluf` to only run arguments that fall into that subjob's \nbin. For example, this command:\n```bash\n$ cluf my_script --bin=0/3\t\t# short option: -b\n```\nwould run `cluf` in direct mode, but only execute iterations falling into bin 0 \nout of 3, i.e., iterations 0, 3, 6, 9, etc. (Bins are zero-indexed.)\n\nYou can use this to start subjobs manually if you like.\nYou can assign multiple bins to one subjob, For example, the option\n`--bins=0-2,5/10` will assign bins 0, 1, 2, and 5 (out of a total of 10 bins).\n\n### If your iterable is not stable\nThe default approach to binning assumes that the arguments iterable will \nyield the same arguments in the same order during execution of each subjob.\nIf you can't ensure that, then binning can be based on the arguments themselves,\ninstead of their order.\n\nThere are two alernative ways to handle binning: using *argument hashing* and\n*direct assignment*.\n\n### Argument hashing\nBy specifying the `--hash` option, you can instruct `cluf` to hash one or more\nof the arguments to determine its bin.\n\nFor example, doing:\n```bash\n$ cluf example --nodes=12 --hash=0\t\t# short options: -n and -x\n```\nwill instruct `cluf` to hash the first argument of each iteration to decide\nwhich bin the iteration belongs to. \n\nBefore hashing, `cluf` calls `str` on the argument.\nIt's important that the argument selected for hashing has a\nstable string representation that reflects its value. Using objects that don't\nimplement `__str__` won't work, both because their string representation\ndoesn't reflect their value, and because their memory address appears within\nit, which will be different in each subjob. However, for this purpose, a list\nwould be considered \"hashable\" (provided it's individual elements are). On the\nother hand, dict's and sets are not suitable, because they are unordered, so\ntheir string representation is not stable. One safe approach is to simply \nprovide one argument that is a unique ID, and select it for hashing.\n\nIdeally the argument selected for hashing should be unique throughout\niteration, since repeated values would be assigned to the same subjob. But\noccaisional repetitions won't imbalance load much. To help achieve uniqueness\nyou can provide combinations of arguments to be hashed. If you provided \narguments as keyword arguments (using an Arguments object) you can select them \ntoo.\n\nFor example, this:\n```bash\n$ cluf example --nodes=12 --hash=0-2,5,my_kwarg\t# short options: -n and -x\n```\nwill hash arguents in positions 0,1,2, and 5, along with the keyword argument \n`my_kwarg`. If any hashed arguments are missing in an \niteration (becase, recall, invocations may use different numbers of arguments), \nthey are simply ommitted when calculating the hash.\n\n### Direct assignment\nThe final method for dividing work is to include an argument that explicitly \nspecifies the\nbin for each iteration. To activate direct assignment, and to specify which\nargument should be interpreted as the bin, use `--key` option:\n\n```bash\n$ cluf example --nodes=12 --key=2 \t\t# short options: -n and -k\n```\nIn the command above, the argument in position 2 (the third argument)\n will be interpreted as\nthe bin for each iteration. You can also specify a keyword argument by name.\n\nYou should only use direct assignment if you really have to, because it's more\nerror prone, and it makes it more difficult to change the number of bins.\nIt also\nintroduces job division logic into your script which `cluf` was designed to\nprevent.\n\n## `cluf_options` and `.clufrc`\nFor more extensive configuration, you can include a dictionary named \n`cluf_options` in your target script to\ncontrol the behavior of `cluf`. This can be more convenient than the command\nline if you have to set a lot of options, and helps to document the options\nyou used. You can also set options globally in a file at `~/.clufrc`. \n\nAll options that can be set on the command line can be set within `cluf_options`\nor `.clufrc`, plus a few extras. There is one exception, however, which is\nthe option to force direct / dispatch mode (`--mode` or `-m`), which can only \nbe set on the command line.\n\n`cluf_options` should be a dictionary whose keys are the long option names,\nand whose values are strings representing the option values as you would enter\nthem on the command line. `.clufrc` should be a valid JSON object, with the\nsame key-value format. See the following examples:\n\n**Using `cluf_options`**\n(in `my_script.py`)\n```python\ncluf_options = {\n\t'hash': (0,1,2,5),\t# Or use '0-2,5'. Applies to .clufrc as well\n\t'nodes': 12,\n\t'queue': True\t\t# Flag options based on truthiness of value\n}\n```\n\n**Using `.clufrc`**\n(in `~/.clufrc`)\n```json\n{\n\t\"hash\": [0,1,2,5],\n\t\"nodes\": 12,\n\t\"queue\": true\n}\n```\n\nIn some cases you may want to use `cluf_options` to simply modify (e.g. add to) \nthe options in `.clufrc`, rather than overriding them. You can access the \n`.clufrc` options by importing them, using `from clusterf_func import RC_PARAMS`.\n\nFor the most part, any option that can be set on the command line can be set in\n`cluf_options` and `.clufrc`, and vice versa, but there are a few options that\ncan *only* be set in `cluf_options` and `.clufrc`. We cover those now along\nwith some options that are just less convenient to set on the command line. \nSee **Reference** for all available options.\n\n### Environment variables\n(Note: This option can be set on the command line but is somewhat less \nconvenient.)\nLet's suppose execution of your target script requires certain environment\nvariables to be set. If you run `cluf` in *direct mode*, there's nothing to\nthink about\u2014your script will inheret the current environment.\n\nFor example, if you did:\n```bash\nMYENV=1 cluf my_script.py\n```\nThe value of `MYENV` would be seen by your script.\n\nHowever, if you run `cluf` in dispatch mode, then the job scripts will\nrun in a different environment. Use the `env` option to specify any \nenvironment variables that should be set when running the subjobs. `env` should\nbe a dictionary within `cluf_options` or `.clufrc` that has variable names\nas keys and values as, well, values:\n\n(in `my_script.py`)\n```python\ncluf_options = {\n\t'env': {\n\t\t'MYENV': 'foo',\n\t\t'OTHER_ENV': 'bar',\n\t}\n}\n```\n\n(in `~/.clufrc`)\n```json\n{\n\t\"env\": {\n\t\t\"MYENV\": \"foo\",\n\t\t\"OTHER_ENV\": \"bar\"\n\t}\n}\n```\n\nIf provided on the commandline, the contents of `env` will be pasted as-is\nin front of the line that runs the subjob within the subjob script. So the\nequivalent `env` specification would be:\n```bash\ncluf my_script.py --nodes=4 --env='MYENV=foo OTHER_ENV=bar'\n```\n\nEach of these methods will cause the invocation of subjobs within subjob\nscripts to look like this:\n```bash\nMYENV=foo OTHER_ENV=bar cluf my_script [...options]\n```\n\nNote that, regardless of where it is specified, if you want to provide a \nvalue for an environment variable that contains a space or other character\nneeding escaping, keep in mind that a round of interpretation by the \ncommand line, Python, or JSON will have occurred (depending on where it was set)\n\nSo, e.g., this won't work:\n```bash\ncluf my_script.py --nodes=4 --env='MYENV=foo bar'\n```\nNor will, this:\n```bash\ncluf my_script.py --nodes=4 --env=MYENV=foo\\ bar\n```\nBut This will work:\n```bash\ncluf my_script.py --nodes=4 --env='MYENV=foo\\ bar'\n```\nAnd so will this:\n```bash\ncluf my_script.py --nodes=4 --env=MYENV=foo\\\\\\ bar\n```\n\n### PBS options\n(This option cannot be set on the command line.)\nPortable Batch System options control how the cluster scheduler schedules your\njob, and allows you to request specific compute resources and specify the\namount of time that your job should run.\nIn general PBS options should be set using a dictionary of key-value pairs\nusing the option name as the key. For example, to request that your\nsubjobs run on compute nodes with at least 4 cpu cores and 2 gpus, you can do\nsomething like this:\n\n(in `my_script.py`)\n```python\ncluf_options = {\n\t'pbs_options': {'ppn': 4, 'gpus': 2}\n}\n```\n\n(in `~/.clufrc`)\n```json\n{\n\t\"pbs_options\": {\"ppn\": 4, \"gpus\": 2}\n}\n```\n\nThe `ppn` (processes per node) option should usually match the number of worker\nprocesses set by the `processes` option (whether set on the command line, \n`cluf_options`, or in `.clufrc`). So if `ppn` isn't explicitly set in your\nPBS options, but `processes` is set, then it will default to the value of \n`processes`. You can still set a different value for ppn, if e.g. your target \nfunction itself spawns proceses.\n\nThere are three special options whose names differ from the \nPBS option names slightly, and these options are set to defaults unless \nspecifically overridden.\n\n- `'name'`: the name of your subjobs as they appear in the job scheduler.\n\tThis is also used to name your subjob scripts (by appending `'.pbs'`).\n\tThe default is the format string `'{target}-{subjob}-{num_subjobs}'`,\n\twith the fields being interpolated by the target module's name, the \n\tsubjob number, and the total number of subjobs respectively.\n\tIf you override\n\tthis you can also use those format fields, and you must at least use\n\tthe `{subjob}` field to ensure that each of your subjobs gets a \n\tunique name (otherwise the subjob scripts will overwrite one another).\n- `'stdout'`: the path at which to place stdout captured from your subjobs,\n\trelative to the `jobs_dir` if set (if not set then it is relative to the \n\tcurrent working directory).\n\tThis defaults to the subjob name plus `'.stdout'`\n\tAs for the `'name'` option, \n\tif you override this, make sure that the paths for subjobs\n\tare unique by using the `{node_num}` field somewhere.\n- `'stderr'`: similar to `'stdout'`. Defaults to the subjob name plus \n\t`'.stderr'`\n\nThe combinations of PBS options that are available and/or required depends on \nthe setup of your cluster. Usually a system is configured with smart defaults\nso that you can queue simple jobs without setting any PBS options.\n\n\n### Additional statements\n(This option can be set on the command line but may be more convenient to set\nwithin your target script.)\nYou can also specify any additional statements that you want to appear in your\nsubjob scripts. This gives you more flexibility than simply setting environment \nvariables. You can include an external script, whose statements get merged\ninto the job script before the line that runs subjob using\n `prepend_script`. Include an external script *after* the line that runs\nthe subjob using the `append_script` option. \n\nIf you just want to add a statement or two, it maybe more convenient to \nput them in your target script (or `.clufrc`).\nThe options `prepend_statements` and `append_statements` can be used provide a \nlist of shell statements that get inserted right before or after the line\nthat actually runs the subjob within subjob scripts. Each element of the list\nshould be a valid shell statement which will appear on its own line when merged\ninto the jobscripts. The options aren't available on the command line.\n\n# Reference\n\nThe `cluf` command has lots of options, which can be specified in three\ndifferent places:\n\n 1. as command line arguments, or\n 2. within your target module, inside a dictionary called `cluf_options`, or\n 3. in the `~/.clufrc` file, in the form of a JSON object\n\nThese locations are in decreasing order of precedence, i.e. the command line\noverrides all other options, and the `.clufrc` file doesn't override the others.\n\nOptions given in the `cluf_options` dictionary in the target module or in the\n`.clufrc` JSON object should be identified by the long option name, without\nthe leading '--'.\n\n\n### All `cluf` options\n\n
\nusage: cluf [-h] [-j JOBS_DIR] [-t TARGET] [-a ARGS] [-q] [-p PROCESSES]\n            [-b BINS] [-e ENV] [-P PREPEND_SCRIPT] [-A APPEND_SCRIPT]\n            [-m {dispatch,direct}] [-x HASH | -k KEY]\n            [-n NODES | -i ITERATIONS]\n            target_module\n\nRun a function many times on multiple processors and machines.\n\npositional arguments:\n  target_module         path to the python module that contains the target\n                        function to be run many times.\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -j JOBS_DIR, --jobs-dir JOBS_DIR\n                        Specify a directory in which to store job scripts and\n                        the files generated from the stdout and stdin during\n                        job execution. This directory will be made if it\n                        doesn't exist. This option only takes effect in\n                        dispatch mode.\n  -t TARGET, --target TARGET\n                        Alternate name for the target callable to be invoked\n                        repeatedly. Default is \"target\".\n  -a ARGS, --args ARGS  Alternate name for the arguments iterable. Default is\n                        \"args\".\n  -q, --queue           Enqueue the generated scripts using qsub. This option\n                        only takes effect in dispatch mode.\n  -p PROCESSES, --processes PROCESSES\n                        Number of processors to use.\n  -b BINS, --bins BINS  Optionally specify a portion of the work to be done.\n                        Should take the form \"x/y\" meaning \"do the x-th\n                        section out of y total sections. For example, \"0/2\"\n                        means divide the work into two halves, and do the\n                        first (0th) half. Note that x and y should be\n                        integers, and x should be from 0 to y-1 inclusive.\n                        This option only takes effect in direct mode.\n  -e ENV, --env ENV     Provide environment variables that should be set when\n                        running sub-jobs. This is for use in dispatch mode,\n                        since job scripts will run in a different environment.\n                        In direct mode, the environment is inherited. The\n                        value of this option should be an enquoted string of\n                        space-separated key=value pairs. For example: $ cluf\n                        my_script -n 12 -e 'FOO=bar BAZ=\"fizz bang\"'will set\n                        FOO equal to \"bar\" and BAZ equal to \"fizz bang\". This\n                        option only takes effect in dispatch mode.\n  -P PREPEND_SCRIPT, --prepend-script PREPEND_SCRIPT\n                        Path to a script whose contents should be included at\n                        the beginning of subjob scripts, being executed before\n                        running the subjob. You can include multiple comma-\n                        separated paths. This option only takes effect in\n                        dispatch mode.\n  -A APPEND_SCRIPT, --append-script APPEND_SCRIPT\n                        Path to a script whose contents should be included at\n                        the end of subjob scripts, being executed after the\n                        subjob completes. You can include multiple comma-\n                        separated paths. This option only takes effect in\n                        dispatch mode.\n  -m {dispatch,direct}, --mode {dispatch,direct}\n                        Explicitly set the mode of operation. Can be set to\n                        \"direct\" or \"dispatch\". In direct mode the job is run,\n                        whereas in dispatch mode a script for the job(s) is\n                        created and optionally enqueued. Setting either -n or\n                        -i implicitly sets the mode of operation to\n                        \"dispatch\", unless specified otherwise.\n  -x HASH, --hash HASH  Specify an argument or set of arguments to be used to\n                        determine which bin an iteration belons in. These\n                        arguments should have a stable string representation\n                        (i.e. no unordered containers or memory addresses) and\n                        should be unique over the argumetns iterable. This\n                        should only be set if automatic binning won't work,\n                        i.e. if your argument iterable is not stable.\n  -k KEY, --key KEY     Integer specifying the positional argument to use as\n                        the bin for each iteration. That key argument should\n                        always take on a value that is an integer between 0\n                        and num_bins-1. This should only be used if you really\n                        need to control binning. Prefer to rely on automatic\n                        binning (if your iterable is stable), or use the\n                        -xoption, which is more flexible and less error-prone.\n  -n NODES, --nodes NODES\n                        Number of compute nodes. This option causes the\n                        command to operate in dispatch mode, unless the mode\n                        is explicitly set\n  -i ITERATIONS, --iterations ITERATIONS\n                        Approximate number of iterations per compute node.\n                        This option causes the command to operate in dispatch\n                        mode, unless the mode is explicitly set. Note that\n                        using this instead of --nodes (-n) can lead to delay\n                        because the total number of iterations has to be\n                        counted to determine the number of compute nodes\n                        needed.\n
", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/enewe101/cluster-func", "keywords": "threading multiprocessing schedule scheduling batch process processing queue queueing cluster qsub pbs", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "cluster-func", "package_url": "https://pypi.org/project/cluster-func/", "platform": "", "project_url": "https://pypi.org/project/cluster-func/", "project_urls": { "Homepage": "https://github.com/enewe101/cluster-func" }, "release_url": "https://pypi.org/project/cluster-func/0.0.6/", "requires_dist": null, "requires_python": "", "summary": "Run a function many times on many processes / machines", "version": "0.0.6" }, "last_serial": 2615312, "releases": { "0.0.0": [ { "comment_text": "", "digests": { "md5": "4adffb00244fd5121de7f79e11cfdf4d", "sha256": "4d4e658250b19d5061df9e0dc25fe2d3b7082d77f507099caa82d184b736bd05" }, "downloads": -1, "filename": "cluster-func-0.0.0.tar.gz", "has_sig": false, "md5_digest": "4adffb00244fd5121de7f79e11cfdf4d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24031, "upload_time": "2017-01-16T03:50:08", "url": "https://files.pythonhosted.org/packages/63/60/8b862865637ac3b221bdcc33d9d726350e943538c1a2d8b7325406cfa816/cluster-func-0.0.0.tar.gz" } ], "0.0.1": [ { "comment_text": "", "digests": { "md5": "4bbc14800a16c289ab226be55086eb21", "sha256": "035e6325817535d9724a66e2d3e72e3cf220ab763e8b56db9a7f271b49088413" }, "downloads": -1, "filename": "cluster-func-0.0.1.tar.gz", "has_sig": false, "md5_digest": "4bbc14800a16c289ab226be55086eb21", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32791, "upload_time": "2017-01-16T03:54:20", "url": "https://files.pythonhosted.org/packages/86/87/22f4b972dcc7c581e47cc68a6ad915aa842516a27677de85ef9902e7d147/cluster-func-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "0079d5ca43b2963fd554658584591c1f", "sha256": "2da0c2e06d2f7407dc913b84abba118c480aeef44d8727dc99552f191f84e52c" }, "downloads": -1, "filename": "cluster-func-0.0.2.tar.gz", "has_sig": false, "md5_digest": "0079d5ca43b2963fd554658584591c1f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32700, "upload_time": "2017-01-16T04:14:33", "url": "https://files.pythonhosted.org/packages/a9/c3/03ddfda46dcd81be80468e434c42fc28de703ae16fcf47f1e51d4aa34507/cluster-func-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "bf0850fe2fac44aa936fe1fe012de5dc", "sha256": "c25ae7fdd2f18e02d1c168bc72705e3f10a44869cf619d6757b3b5024269e502" }, "downloads": -1, "filename": "cluster-func-0.0.3.tar.gz", "has_sig": false, "md5_digest": "bf0850fe2fac44aa936fe1fe012de5dc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32680, "upload_time": "2017-01-16T04:20:26", "url": "https://files.pythonhosted.org/packages/b7/55/77af69f04ab2fd4de10085fd48a32e7cc21a9e0c265a9638d1c2117729cf/cluster-func-0.0.3.tar.gz" } ], "0.0.4": [ { "comment_text": "", "digests": { "md5": "b461138b123044e4767a748e96c77180", "sha256": "7e4580a8aa636069d99c32986be83463a1507b48ed86fd90c146ceb299b4df3f" }, "downloads": -1, "filename": "cluster-func-0.0.4.tar.gz", "has_sig": false, "md5_digest": "b461138b123044e4767a748e96c77180", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33132, "upload_time": "2017-01-16T17:12:48", "url": "https://files.pythonhosted.org/packages/a0/f9/2fe92bf9d491e591ec2c24af01a64a2931e0093a020a936635942e456fbc/cluster-func-0.0.4.tar.gz" } ], "0.0.5": [ { "comment_text": "", "digests": { "md5": "41498357cadfbb8cd93505e006f31eed", "sha256": "4f6b7df4fa77e6fe078ede967aa8d95b9d0cb702e9139df93e9c250492edc248" }, "downloads": -1, "filename": "cluster-func-0.0.5.tar.gz", "has_sig": false, "md5_digest": "41498357cadfbb8cd93505e006f31eed", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33162, "upload_time": "2017-01-18T02:44:23", "url": "https://files.pythonhosted.org/packages/fc/ba/594fdda91df58998523f5f4dd9af4f1f51d4ddbc44666909279ae1fb3ebf/cluster-func-0.0.5.tar.gz" } ], "0.0.6": [ { "comment_text": "", "digests": { "md5": "462b622d6b749a2454759cd3890ac87b", "sha256": "d631a026f78fdaef331d2388009782a9464af8108c52be6b3cfb30fff3a24cb0" }, "downloads": -1, "filename": "cluster-func-0.0.6.tar.gz", "has_sig": false, "md5_digest": "462b622d6b749a2454759cd3890ac87b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33459, "upload_time": "2017-02-02T20:14:27", "url": "https://files.pythonhosted.org/packages/44/de/47a7afa7ce65027b5a1509afbabf09fc601fc3d6c8e6203069976b96611f/cluster-func-0.0.6.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "462b622d6b749a2454759cd3890ac87b", "sha256": "d631a026f78fdaef331d2388009782a9464af8108c52be6b3cfb30fff3a24cb0" }, "downloads": -1, "filename": "cluster-func-0.0.6.tar.gz", "has_sig": false, "md5_digest": "462b622d6b749a2454759cd3890ac87b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33459, "upload_time": "2017-02-02T20:14:27", "url": "https://files.pythonhosted.org/packages/44/de/47a7afa7ce65027b5a1509afbabf09fc601fc3d6c8e6203069976b96611f/cluster-func-0.0.6.tar.gz" } ] }