{ "info": { "author": "David Chen", "author_email": "mvjome@gmail.com", "bugtrack_url": null, "classifiers": [ "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "![Luiti](https://raw.githubusercontent.com/Luiti/luiti.github.io/master/images/luiti/luiti_rectangle_logo.png)\n========================\n[![Build Status](https://travis-ci.org/Luiti/luiti.svg?branch=master)](https://travis-ci.org/luiti/luiti)\n[![Coverage Status](https://coveralls.io/repos/Luiti/luiti/badge.svg?branch=master&service=github)](https://coveralls.io/github/Luiti/luiti?branch=master)\n[![Health](https://landscape.io/github/luiti/luiti/master/landscape.svg?style=flat)](https://landscape.io/github/luiti/luiti/master)\n[![Download](https://img.shields.io/pypi/dm/luiti.svg?style=flat)](https://pypi.python.org/pypi/luiti)\n[![License](https://img.shields.io/pypi/l/luiti.svg?style=flat)](https://pypi.python.org/pypi/luiti)\n\nAs [Luigi](https://github.com/spotify/luigi)'s homepage said, it's \"a\nPython module that helps you build complex pipelines of batch jobs. It\nhandles dependency resolution, workflow management, visualization etc.\nIt also comes with Hadoop support built in.\"\n\nLuiti is built on top of Luigi, separates all your tasks into **multiple\npackages**, and forces **one task per one Python file**. Luiti task classes\ncan be managed by the `luiti` command, supported operations are ls, new,\ngenerate, info, clean, run, and webui.\n\nLuiti is born to build **a layered database warehouse**, corresponding to\nthe different packages we just mentioned. A data warehouse consists\nof synced data sources, fact tables, dimension tables, regular or\ntemporary business reports.\n\nThe essence of batching processing system is to separate a large task\ninto small tasks, and the essence of business report is that a daily\nreport or a weekly report is requried, so here comes TaskDay, TaskWeek,\nand more. Task classes also have a Hadoop version, such as TaskDayHadoop,\nTaskWeekHadoop, and so on.\n\nYou can pass any parameters into Luigi's tasks, but Luiti recommend you\nto pass only `date_value` parameter. So you can run Luiti tasks\nperiodically, e.g. hourly, daily, weekly, etc. **luiti = luigi + time**.\n\n\nDocument guide\n------------------------\n1. [Intro](#luiti)\n3. [Luiti command tool](#luiti-command-tool)\n2. [Luiti WebUI screenshots](#luiti-webui-screenshots)\n4. [Core concepts based on time management](#core-concepts-based-on-time-management)\n5. [Built-in properties](#task-specification-and-built-in-properties-and-recommendation)\n6. [Manage multiple projects in luiti](#manage-multiple-projects-in-luiti)\n7. [A simple guide to Luigi](#a-simple-guide-to-luigi)\n8. [A simple example in luiti](#a-simple-example-in-luiti)\n9. [Installment and Development](#installment-and-develop-requirements)\n10. [Task recommendation](#task-recommendation)\n11. [Task decorators](#task-decorators)\n12. [MapReduce related](#mapreduce-related)\n13. [Extend luiti](#extend-luiti)\n14. [FAQ](#faq)\n15. [Run tests](#run-tests)\n16. [Who uses Luiti?](#who-uses-luiti)\n\nKeynote [Luiti - An Offline Task Management Framework](https://speakerdeck.com/mvj3/luiti-an-offline-task-management-framework)\n\nLuiti command tool\n------------------------\nAfter installed package, you can use `luiti` command tool that contained\nin the package.\n\n```text\n$ luiti\nusage: luiti [-h] {ls,new,generate,info,clean,run,webui} ...\n\nLuiti tasks manager.\n\noptional arguments:\n -h, --help show this help message and exit\n\nsubcommands:\n valid subcommands\n\n {ls,new,generate,info,clean,run,webui}\n ls list all current luiti tasks.\n new create a new luiti project.\n generate generate a new luiti task python file.\n info show a detailed task.\n clean manage files that outputed by luiti tasks.\n run run a luiti task.\n webui start a luiti DAG visualiser.\n```\n\nLuiti WebUI screenshots\n------------------------\n```bash\n./example_webui_run.py\n# or\nluiti webui --project-dir your_main_luiti_package_path\n```\n\nHere's some screenshots from example_webui_run.py, just to give you an\nidea of how luiti's multiple Python packages works.\n\n1. Luiti WebUI list\n![Luiti WebUI list](https://raw.githubusercontent.com/luiti/luiti/master/screenshots/luiti_webui_list.png)\n\n2. Luiti WebUI show\n![Luiti WebUI show](https://raw.githubusercontent.com/luiti/luiti/master/screenshots/luiti_webui_show.png)\n\n3. Luiti Code show\n![Luiti Code show](https://raw.githubusercontent.com/luiti/luiti/master/screenshots/luiti_code_show.png)\n\n\nCore concepts based on time management\n------------------------\n### date type\n\n#### Basic inheriting task classes:\n0. TaskBase (luigi.Task)\n1. TaskHour (TaskBase)\n2. TaskDay (TaskBase)\n3. TaskWeek (TaskBase)\n4. TaskMonth (TaskBase)\n5. TaskRange (TaskBase)\n\nYou can extend more date type by subclass `TaskBase`, and make sure the\ndate types are added in `TaskBase.DateTypes` too.\n\n#### Hadoop ineriting task classes:\n1. TaskDayHadoop (luigi.hadoop.HadoopExt, TaskDay)\n2. TaskWeekHadoop (luigi.hadoop.HadoopExt, TaskWeek)\n3. TaskRangeHadoop (luigi.hadoop.HadoopExt, TaskRange)\n\n#### Other task classes:\n1. RootTask (luigi.Task)\n2. StaticFile (luigi.Task)\n3. MongoImportTask (TaskBase) # export json file from hdfs to mongodb.\n\n\n\nTask specification and built-in properties and recommendation\n------------------------\n### Task naming conventions\n1. One Task class per file.\n2. Task class should be camel case ( e.g. `EnglishStudentAllExamWeek`), file name should be low case with underscore ( e.g. `english_student_all_exam_week.py` ).\n3. Task files should be under the directory of `luiti_tasks`. luiti use this convertion to linking tasks inner and outer of pacakges.\n4. Task class name should be ended with date type, e.g. Day, Week, etc. Please refer to `TaskBase.DateTypes`.\n\n\n### Task builtin properties.\n1. `date_value`. Required, even it's a Range type Task. This ensure that `output` will be written to a day directory.\n2. `data_file`. The absolute output file path, it's a string format.\n3. `data_dir`. The directory of the absolute output file path, it's a string format.\n4. `root_dir`. The root path of this package. `data_file` and `data_dir` are all under it.\n5. `output`. Basic Task's output class is LocalTarget, and Hadoop Task's output class is hdfs.HdfsTarget.\n6. `date_str`. A datetime string, such as \"20140901\".\n7. `date_type`. A string that generated from task class name, e.g. Day, Week, etc.\n8. `date_value_by_type_in_last`. If current date type is Week, and it'll return the previous week's `date_value`.\n8. `date_value_by_type_in_begin`. If current date type is Week, and it'll return Monday zero clock in the current week.\n9. `date_value_by_type_in_end`. If current date type is Week, and it'll return Sunday 11:59:59 clock in the current week.\n10. `pre_task_by_self`. Usually it returns previous task in the current date type. If reaches the time boundary of current date type, it returns RootTask.\n11. `is_reach_the_edge`. It's semester at 17zuoye business.\n12. `instances_by_date_range`. Class function, return all task intances list that belongs to current date range.\n13. `task_class`. Return current task class.\n\n\n\nManage multiple projects in luiti\n------------------------\n#### The directory structure of a specific project.\n\nWe recommend you to organize every project's directory structure as the\nbelow form, and it means it's also a normal Python package, for example:\n\n```text\nproject_A --- project directory\n setup.py --- Python package install script\n README.markdown --- project README\n project_A/ --- Python package install directory\n \u251c\u2500\u2500 __init__.py --- mark current directories on disk as a Python package directories\n \u2514\u2500\u2500 luiti_tasks --- a directory name which indicates it contains several luiti tasks\n \u251c\u2500\u2500 __init__.py --- mark current directories on disk as a Python package directories\n \u251c\u2500\u2500 __init_luiti.py --- initialize luiti environment variables\n \u251c\u2500\u2500 exam_logs_english_app_day.py --- an example luiti task\n \u251c\u2500\u2500 ..._day.py --- another example luiti task\n \u2514\u2500\u2500 templates --- some libraries\n \u251c\u2500\u2500 __init__.py\n \u00a0\u00a0 \u00a0\u00a0 \u2514\u2500\u2500 ..._template.py\n```\n\nAfter installing `luiti`, you can run following command line to generate\na project like above.\n```bash\nluiti new --project-name project_A\n```\n\nIf other luiti projects need to using this package, and you need to\ninstall this package, to make sure luiti could find them in the\nsearch path (`sys.path`) of Python modules.\n\n\n#### How to link current Task to another Task that belongs to another pacakge?\nEvery luiti projects share the same structure, e.g.\n`project_A/luiti_tasks/another_feature_day.py`. After config\n`luigi.plug_packages(\"project_B\", \"project_C==0.0.2\"])` in\n`__init_luiti.py`, you can use `@luigi.ref_tasks(\"ArtistStreamDay')` to\nindicate current Task to find `ArtistStreamDay` Task in current package\n`project_A`, or related `project_B`, `project_C` packages.\n\n\n\nA simple guide to Luigi\n------------------------\nLuigi's core concept is to force you to separte a big task into many small\ntasks, and they're linked by **atomic** Input and Ouput. Luigi contains four\nparts mainly:\n\n1. **Output**. It must be implemented in `output` function, such as `LocalTarget` and `hdfs.HdfsTarget`.\n2. **Input**. It must be implemented in `requires` function, and the\n function is supposed to return some or None task instances.\n3. **Parameters**. Parameters should be inherited from `luigi.Parameter`,\n e.g. `DateParameter`, etc.\n4. **Execute Logic**. Use `run` function if running at local, or `mapper` and `reducer`\n if running on a distributed MapReduce YARN.\n\nAfter finish the business logic implementation and test cases, You can\nsubmit your task to the `luigid` background daemon. `luigid` will\nprocess task dependencies automatically, this is done by checking\n`output` is already `exists` (It's the Target class's function). And\nluigi will guarantee that task instances are uniq in current\n`luigid` background process by the task class name and parameters.\n\nA simple example in luiti\n------------------------\n#### An official example from luigi.\nCode below is copied from http://luigi.readthedocs.org/en/latest/example_top_artists.html\n\n```python\nimport luigi\nfrom collections import defaultdict\n\nclass AggregateArtists(luigi.Task):\n date_interval = luigi.DateIntervalParameter()\n\n def output(self):\n return luigi.LocalTarget(\"/data/artist_streams_%s.tsv\" % self.date_interval)\n\n def requires(self):\n return [Streams(date) for date in self.date_interval]\n\n def run(self):\n artist_count = defaultdict(int)\n\n for input in self.input():\n with input.open('r') as in_file:\n for line in in_file:\n timestamp, artist, track = line.strip().split()\n artist_count[artist] += 1\n\n with self.output().open('w') as out_file:\n for artist, count in artist_count.iteritems():\n print >> out_file, artist, count\n```\n\n#### The same example written in luiti.\n\n* First file: `artist_project/luiti_tasks/artist_stream_day.py`\n\n```python\nfrom luiti import *\n\nclass ArtistStreamDay(StaticFile):\n\n @cached_property\n def filepath(self):\n return TargetUtils.hdfs(\"/tmp/streams_%s.tsv\" % self.date_str\n```\n\n* Second file: `artist_project/luiti_tasks/aggregate_artists_week.py`\n\n```python\nfrom luiti import *\n\n@luigi.ref_tasks(\"ArtistStreamDay')\nclass AggregateArtistsWeek(TaskWeek):\n\n def requires(self):\n return [self.ArtistStreamDay(d1) for d1 in self.days_in_week]\n\n def output(self):\n return TargetUtils.hdfs(\"/data/artist_streams_%s.tsv\" % self.date_str\n\n def run(self):\n artist_count = defaultdict(int)\n\n for file1 in self.input():\n for line2 in TargetUtils.line_read(file1):\n timestamp, artist, track = line.strip().split()\n artist_count[artist] += 1\n\n with self.output().open('w') as out_file:\n for artist, count in artist_count.iteritems():\n print >> out_file, artist, count\n```\n\nOptimizition notes:\n\n1. luiti's task class is built in with `date_value` property, and converted\n into `Arrow` data type.\n2. In ArtistStreamDay, `date_str` is transformed from `date_value`, and\n converted from a function into a instance property after the first call.\n3. `@luigi.ref_tasks` bind ArtistStreamDay as AggregateArtistsWeek's\n instance property, so we can use `self.ArtistStreamDay(d1)` form to\n instantiate some task instances.\n4. After AggregateArtistsWeek is inherited from `TaskWeek`, it'll has\n `self.days_in_week` property automatically.\n5. `TargetUtils.line_read` replaced original function that needs two\n lines codes to complete the feature, and return a Generator directly.\n\n\n#### Writing MapReduce in luiti\n* MapReduce file: `artist_project/luiti_tasks/aggregate_artists_week.py`\n\n```python\nfrom luiti import *\n\n@luigi.ref_tasks(\"ArtistStreamDay')\nclass AggregateArtistsWeek(TaskWeekHadoop):\n\n def requires(self):\n return [self.ArtistStreamDay(d1) for d1 in self.days_in_week]\n\n def output(self):\n return TargetUtils.hdfs(\"/data/weeks/artist_streams_%s.tsv\" % self.date_str\n\n def mapper(self, line1):\n timestamp, artist, track = line.strip().split()\n yield artist, 1\n\n def reducer(self, artist, counts):\n yield artist, len(counts)\n```\n\nYes, it's almost no difference to luigi, except the `self.days_in_week`\nproperty and `@luigi.ref_tasks` decorator.\n\n\nInstallment and Develop requirements\n------------------------\n#### Installment\n```bash\npip install luiti\n```\n\nOr lastest source code\n\n```bash\ngit clone https://github.com/luiti/luiti.git\ncd luiti\npython setup.py install\n```\n\n#### Develop requirements\n1. [Node.js](https://nodejs.org/download/) & [bower](http://bower.io/#install-bower)\n2. pip requirements from setup.py\n3. [tox](https://testrun.org/tox/latest/) & [nose](https://nose.readthedocs.org/)\n\n\nTime library\n-------------------------\n\nThe time library is [Arrow](http://crsmithdev.com/arrow/) , every Task\ninstance's `date_value` property is a arrow.Arrow type.\n\nluiti will convert date paramters into local time zone automatically. If\nyou want to customize time, please prefer to use\n `ArrowParameter.get(*strs)` and `ArrowParameter.now()` to make sure you\n use the local time zone.\n\n\n\nTask recommendation\n-------------------------\n#### Cache\nWe highly recommend you to use `cached_property`, like\n[werkzeug](http://werkzeug.pocoo.org/docs/0.10/utils/) said, \"A decorator that\nconverts a function into a lazy property. The function wrapped is called\nthe first time to retrieve the result and then that calculated result is\nused the next time you access the value\".\n\nThis function is heavily used in 17zuoye everyday, we use it to cache\nlots of things, such as a big data dict.\n\n```python\nclass AnotherBussinessDay(TaskDayHadoop):\n\n def requires(self):\n return [task1, task2, ...]\n\n def mapper(self, line1):\n k1, v1 = process(line1)\n yield k1, v1\n\n def reducer(self, k1, vs1):\n for v1 in vs1:\n v2 = func2(v1, self.another_dict)\n yield k1, v2\n\n @cached_property\n def another_dict(self):\n # lots of cpu/io\n return big_dict\n```\n\n#### Global utilities.\n1. Basic utilities, such as os, re, json, defaultdict, etc.\n2. Date processing utilities, they are arrow, ArrowParameter.\n3. Cache utilities, `cached_property`.\n4. Other utilities, such as IOUtils, DateUtils, TargetUtils, HDFSUtils, MRUtils, MathUtils,\n CommandUtils, CompressUtils.\n\n\nTask decorators\n------------------------\n```python\n# 1. Bind related tasks lazily, and can be used as instance property directly.\n@luigi.ref_tasks(*tasks)\n\n# 2. Support multiple file output in MapReduce\n@luigi.multiple_text_files()\n\n# 3. Run MapReduce in local mode by only add one decorator.\n@luigi.mr_local()\n\n# 4. Check current task' data source's date range is satisfied.\n@luigi.check_date_range()\n\n# 5. Check current task can be runned in current date range.\n@luigi.check_runtime_range(hour_num=[4,5,6], weekday_num=[1])\n\n# 6. Let Task Templates under [luigi.contrib](https://github.com/spotify/luigi/tree/master/luigi/contrib) to follow with Luiti's Task convertion.\n@luigi.as_a_luiti_task()\n\nclass AnotherBussinessDay(TaskDayHadoop):\n pass\n```\n\n\n\nMapReduce related\n------------------------\n#### Clean temporary file when a task fails.\nWhen executing a MR job, luigi will write result to a file with\ntimestamp instantly. If the task successes, then rename to the name that\nthe task's original output file path. If the task fails, then YARN will\ndelete the temporary file automatically.\n\n#### Read file in a Generator way.\n1. Original way. `for line1 in TargetUtils.line_read(hdfs1)`, `line1` is an\n unicode type.\n2. Read by JSON. `for json1 in TargetUtils.json_read(hdfs1)`, `json1` is\n a valid Python object.\n3. Read in a K-V format. `for k1, v1 in TargetUtils.mr_read(hdfs1)`, `k1`\n is an unicode type, and `v1` is a Python object.\n\n#### HDFS file object\nWe recommend to use `TargetUtils.hdfs(path1)`. This function compacts\nthe MR file result data format that consists of \"part-00000\" file blocks.\n\n\n#### MapReduce test cases\n\n1. Add MapReduce input and output to `mrtest_input` and `mrtest_output`,\n these mimic the MapReduce processing.\n2. In your test file, use `@MrTestCase` to decorator your test class,\n and add your task class to `mr_task_names` list.\n3. (Optional) Add some config dict to `mrtest_attrs` to mimic properties\n that generated in production mode.\n4. Run your test cases!\n\n`buy_fruit_day.py`\n\n```python\nfrom luiti import *\n\nclass BuyFruitDay(TaskDayHadoop):\n\n def requries(self):\n ...\n\n def output(self):\n ...\n\n def mapper(self, line):\n ...\n yield uid, fruit\n\n def reducer(self, uid, fruits):\n price = sum([self.price_dict[fruit] for fruit in fruits])\n yield \"\", MRUtils.str_dump({\"uid\": uid, \"price\": price})\n\n @cache_property\n def price_dict(self):\n result = dict()\n for json1 in TargetUtils.json_read(a_fruit_price_list_file):\n result[json1[\"name\"]] = json1[\"price\"]\n return result\n\n def mrtest_input(self):\n return \"\"\"\n{\"uid\": 3, \"fruit\": \"Apple\"}\n{\"uid\": 3, \"fruit\": \"Apple\"}\n{\"uid\": 3, \"fruit\": \"Banana\"}\n \"\"\"\n\n def mrtest_output(self):\n return \"\"\"\n{\"uid\": 3, \"price\": 7}\n \"\"\"\n\n def mrtest_attrs(self):\n return {\n \"price_dict\": {\n \"Apple\": 3,\n \"Banana\": 1,\n }\n }\n\n```\n\n`test file`\n\n```python\nfrom luiti import MrTestCase\n\n@MrTestCase\nclass TestMapReduce(unittest.TestCase):\n mr_task_names = [\n 'ClassEnglishAllExamWeek',\n ...\n ]\n\nif __name__ == '__main__':\n unittest.main()\n```\n\n\nExtend luiti\n------------------------\nUsing `TaskBase`'s builtin `extend` class function to extend or overwrite\nthe default properties or functions, for example:\n\n```python\nTaskWeek.extend({\n 'property_1' : lambda self: \"property_2\",\n})\n```\n\n`extend` class function compacts with `function`, `property`, `cached_property`,\nor any other attributes at the same time\u3002When you want to overwrite\n`property` and `cached_property`, you just need a function value, and\n`extend` will automatically converted into `property` and\n`cached_property` type.\n\n\nFAQ\n------------------------\nQ: How atomic file is supported?\n\nA: As luigi's document mentioned that \"Simple class that writes to a temp file and moves it on close()\n Also cleans up the temp file if close is not invoked\", so use the `self.input().open(\"r\")` or\n `self.output().open(\"w\")` instead of `open(\"some_file\", \"w\")`.\n\nQ: Can luigi detect the interdependent tasks?\n\nA: It's not question inside of luigi, but it's a question about [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting)\n as a general computer science topic. The task scheduler is implemented at `luigi/scheduler.py` .\n\nQ: How to pass more parameters into luiti tasks?\n\nA: You can create a key-value dict, `date_value` is the key, and your\ncustomize parameters are the values.\n\n\n\nIf you have other unresolved questions, please feel free to ask\nquestions at [issues](https://github.com/luiti/luiti/issues).\n\n\nRun tests\n------------------------\n\n```bash\nnosetests\n# or\nnosetests --with-coverage --cover-inclusive --cover-package=luiti\ntox -e py27-cdh\n```\n\n\n\nWho uses Luiti?\n---------------\n* [17zuoye](http://www.17zuoye.com/) Luiti born at this company.\n\nPlease let us know if your company wants to be featured on this list!\n\n\nLicense\n------------------------\nMIT.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/luiti/luiti/", "keywords": null, "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "luiti", "package_url": "https://pypi.org/project/luiti/", "platform": "any", "project_url": "https://pypi.org/project/luiti/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://github.com/luiti/luiti/" }, "release_url": "https://pypi.org/project/luiti/0.2.2/", "requires_dist": null, "requires_python": null, "summary": "Luiti = Luigi + time", "version": "0.2.2" }, "last_serial": 1808985, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "db8499f2b3b52a188276b498f7d8c7ac", "sha256": "0cc8e305d8199e59cae09a50bf3d978f424ddbbbcf728a9a6e04ed91343f6fc5" }, "downloads": -1, "filename": "luiti-0.0.1.tar.gz", "has_sig": false, "md5_digest": "db8499f2b3b52a188276b498f7d8c7ac", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18815, "upload_time": "2015-01-23T07:06:29", "url": "https://files.pythonhosted.org/packages/cc/34/5a012c57642eeb3fd628eef96e2e191e0a3a5c3e3503dc33e7a779f6feee/luiti-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "2060272bec610ac14ca62e3f32334c47", "sha256": "b6f23d416524cfe28900b0357ae23b54a854b17adf91941ebb8234b58aae9b9a" }, "downloads": -1, "filename": "luiti-0.0.2.tar.gz", "has_sig": false, "md5_digest": "2060272bec610ac14ca62e3f32334c47", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19448, "upload_time": "2015-02-03T11:52:09", "url": "https://files.pythonhosted.org/packages/d4/77/e2b77287ef283f2768e99a61085aab55d10404fa00c7cc4e148e2a320ceb/luiti-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "c31dc9d608725059ebdd7121dd6acd1c", "sha256": "6363f56c17fa43852cd22508ffa667f1455b279e7017b77ed420d47edc967759" }, "downloads": -1, "filename": "luiti-0.0.3.tar.gz", "has_sig": false, "md5_digest": "c31dc9d608725059ebdd7121dd6acd1c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23374, "upload_time": "2015-03-20T14:12:44", "url": "https://files.pythonhosted.org/packages/9d/b4/ef2a03e1e68e6f185c835fb8a78e92fd4fefd93c79c604ffe56b3333c04b/luiti-0.0.3.tar.gz" } ], "0.1.0": [ { "comment_text": "", "digests": { "md5": "398ab6829a283d3d1d0c5c6d5c3b2b1f", "sha256": "1f6207528d0a3aff6c55e48a657a1e2f5fcaec8269c914391c2f5d95ddfd39a1" }, "downloads": -1, "filename": "luiti-0.1.0.tar.gz", "has_sig": false, "md5_digest": "398ab6829a283d3d1d0c5c6d5c3b2b1f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23450, "upload_time": "2015-03-24T03:57:22", "url": "https://files.pythonhosted.org/packages/a4/f4/ec515a9b97af8194dec4e6e17625a7f59966ecc181f4c404f718119603a3/luiti-0.1.0.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "b214371f6373b7b24b50ac7208414e39", "sha256": "85c3e3b3728e856174d09e756ec9c4a8620e7e9e194b1f2973f27a57288be1b6" }, "downloads": -1, "filename": "luiti-0.1.2.tar.gz", "has_sig": false, "md5_digest": "b214371f6373b7b24b50ac7208414e39", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31662, "upload_time": "2015-04-20T08:27:41", "url": "https://files.pythonhosted.org/packages/f8/91/ef2a24f169c37ab93a05432a2a7fee3dbe2ca0475916563f92ecdec7e5af/luiti-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "deeffaa5bc9e0e7f92200e016d046713", "sha256": "15ab5d34c562d007822ad086bb53ed1947d06e1f9c0e032a0202eb30c29e9bad" }, "downloads": -1, "filename": "luiti-0.1.3.tar.gz", "has_sig": false, "md5_digest": "deeffaa5bc9e0e7f92200e016d046713", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32788, "upload_time": "2015-04-26T08:31:36", "url": "https://files.pythonhosted.org/packages/04/c6/dd1532d6756f39601dc5a0c8f3b40ea8317e1b414d8e50d0e3fc6cdad0cd/luiti-0.1.3.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "63b97205e10aaad7d6281b6899a2b6c3", "sha256": "af50641116090777a902581a2845cf7121d2d3d59faa35125b64ee954e21357a" }, "downloads": -1, "filename": "luiti-0.1.4.tar.gz", "has_sig": false, "md5_digest": "63b97205e10aaad7d6281b6899a2b6c3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33982, "upload_time": "2015-05-10T06:32:27", "url": "https://files.pythonhosted.org/packages/a2/ea/0cc6dd736fd461d8acd17db60c81b267384ec259dad1e042f2a343ad068a/luiti-0.1.4.tar.gz" } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "9a1e02f22e9c3bbc5ad9deefc7529348", "sha256": "578e4f4f3652c15f93d0650c65b2e911dc8b12f16afd3fb012c7bfd65690dd3d" }, "downloads": -1, "filename": "luiti-0.1.5.tar.gz", "has_sig": false, "md5_digest": "9a1e02f22e9c3bbc5ad9deefc7529348", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5635956, "upload_time": "2015-07-07T06:28:25", "url": "https://files.pythonhosted.org/packages/c0/98/9989b0a37c95906145bc8b0b064455c6dd49c972cd9d4d53cbcdec5da794/luiti-0.1.5.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "35db9c36341471b3b74c0a88f0d2b83c", "sha256": "18364780055d5d41de797f00b1b0e7917b3c068bdc000b6aee49c59139fcfdfd" }, "downloads": -1, "filename": "luiti-0.2.0.tar.gz", "has_sig": false, "md5_digest": "35db9c36341471b3b74c0a88f0d2b83c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5636053, "upload_time": "2015-07-07T06:57:24", "url": "https://files.pythonhosted.org/packages/9c/83/a6058261bfa6d4d91a65c2c442e76ed8461eb38a851808bc37299289605b/luiti-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "04db3dae53d847258db0d2f45a91811e", "sha256": "d36f9a874f37a46b3c4d4522d3a219b89327a5d64ae2e69a68ba02f342ea06b7" }, "downloads": -1, "filename": "luiti-0.2.1.tar.gz", "has_sig": false, "md5_digest": "04db3dae53d847258db0d2f45a91811e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5639691, "upload_time": "2015-07-15T02:05:36", "url": "https://files.pythonhosted.org/packages/93/6f/823950777c4041e6dacb060bb6ced23916be68eaff574891175ec710c9fe/luiti-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "41f9c9896fed77725fddb8611a88a4be", "sha256": "bc9b1811346dcfb695f8c3d4bbfffc701b7d32c4b768d95e2ac3380afccacbc5" }, "downloads": -1, "filename": "luiti-0.2.2.tar.gz", "has_sig": false, "md5_digest": "41f9c9896fed77725fddb8611a88a4be", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5643718, "upload_time": "2015-11-10T04:02:50", "url": "https://files.pythonhosted.org/packages/a6/84/28de8fcd3f4a59f0529c247f9ec04bac6e042f513505beb12c9f4447758e/luiti-0.2.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "41f9c9896fed77725fddb8611a88a4be", "sha256": "bc9b1811346dcfb695f8c3d4bbfffc701b7d32c4b768d95e2ac3380afccacbc5" }, "downloads": -1, "filename": "luiti-0.2.2.tar.gz", "has_sig": false, "md5_digest": "41f9c9896fed77725fddb8611a88a4be", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5643718, "upload_time": "2015-11-10T04:02:50", "url": "https://files.pythonhosted.org/packages/a6/84/28de8fcd3f4a59f0529c247f9ec04bac6e042f513505beb12c9f4447758e/luiti-0.2.2.tar.gz" } ] }