{ "info": { "author": "StephenKwen", "author_email": "hyuncankun@outlook.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: SQL" ], "description": "# Zugh\n\n**[WIP] Access to database in pythonic way**\n\n- [Zugh](#zugh)\n - [Status](#status)\n - [Required](#required)\n - [Licence](#licence)\n - [Install](#install)\n- [Usage](#usage)\n - [Connection](#connection)\n - [Config](#config)\n - [Pool](#pool)\n - [Database](#database)\n - [Table](#table)\n - [Query Object](#query-object)\n - [Insert](#insert)\n - [Insert a Row](#insert-a-row)\n - [Insert Ignore](#insert-ignore)\n - [Insert Or Update](#insert-or-update)\n - [Insert Multi Rows](#insert-multi-rows)\n - [Select](#select)\n - [Filter](#filter)\n - [Logic Express](#logic-express)\n - [Compare](#compare)\n - [Alias](#alias)\n - [Sort](#sort)\n - [Limit](#limit)\n - [Aggregate](#aggregate)\n - [Distinct](#distinct)\n - [Subquery](#subquery)\n - [Join In](#join-in)\n - [Union](#union)\n - [Update](#update)\n - [F Object](#f-object)\n - [Delete](#delete)\n - [Decorator](#decorator)\n - [db.query.query](#dbqueryquery)\n - [db.query.transaction](#dbquerytransaction)\n - [String](#string)\n - [S Object](#s-object)\n - [Math](#math)\n\n**Zugh** is a tool for generating SQL and accessing databases flexibly in pythonic way. It empower you to use complex SQL, but didn't need to write them directly.\n\n## Status\n\nWork in progress.\n\nNow we support MySQL only.\n\n## Required\n\n- Python >= 3.6\n- PyMySQL >= 0.9.3\n\n## Licence\n\nMIT.\n\n## Install\n\nUse pip:\n\n```sh\npip install zugh\n```\n\n# Usage\n\n>Note !\\\n>The time of writing each part of this document is out of order. So the results before and after the execution of SQL may not match. Nevertheless, I recommend that you start reading from scratch and try the code.\n\n## Connection\n\n### Config\n\n```py\n>>> from zugh.db.connection import connect_config\n>>> conn_config = connect_config('localhost', 'your_username', 'your_password')\n# You can use conn_config dict to configure connection for DdataBase object\n# or initial a connection pool\n```\n\n### Pool\n\n```py\n>>> from zugh.db.pool import ConnectionPool\n>>> pool = ConnectionPool(conn_config)\n```\n\n## Database\n\nCreate a database:\n\n```py\n>>> from zugh.schema.db import DataBase\n>>> db = DataBase('zugh', conn_config=conn_config)\n# or db = DataBase('zugh', pool=pool)\n>>> db.create()\n```\n\n## Table\n\nCreate a table.\n\nWe haven't implemented those APIs to create a table yet, so just execute SQL with a connection:\n\n```py\n>>> from zugh.db import connect\n>>> sql = \"\"\"\nCREATE TABLE zugh.users (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `age` int(11) NOT NULL,\n `score` int(11) NOT NULL DEFAULT '0',\n PRIMARY KEY (`id`)\n) Engine=InnoDB DEFAULT CHARSET=utf8mb4;\n\"\"\"\n>>> conn = connect(conn_config) # return a connection context\n>>> with conn as cn:\n with cn.cursor() as cursor:\n cursor.execute(sql)\n```\n\nInitial a `Table` object:\n\n```py\n>>> from zugh.schema.table import Table\n>>> tb = Table('users', db)\n```\n\n## Query Object\n\n`zugh.query.core.QueryBase` provide a base class for Query class below:\n\n- `zugh.query.core.SelectBase`\n- `zugh.query.core.Update`\n- `zugh.query.core.Insert`\n- `zugh.query.core.Delete`\n- or subclass of above class\n\n`Query object` is a instance of above class. If they were printed, a string of SQL statement would output.If configure properly, they can call `.exe()` method to execute. Usually, you don't use them\ndirectly.\n\nMostly, you would initial a `zugh.schema.table.Table` instance and call relative method, then will return a new `Query object`.\n\nDangerous queries, such as `update`, `delete` or similar mothed are expose after `Table.where()` method.\n\n```py\n>>> q_1 = tb.where().select()\n>>> type(q_1)\n\n>>> q_2 = tb.where().update(age=10)\n>>> type(q_2)\n\n>>> q_3 = tb.where().delete()\n>>> type(q_3)\n\n>>> q_4 = tb.insert(dict(age=10, score=18))\n>>> type(q_4)\n\n>>> q_5 = q_1.order_by()\n>> type(q_5)\n\n```\n\n## Insert\n\n### Insert a Row\n\n```py\n>>> q1 = tb.insert(age=16, score=7)\n>>> print(q1)\nINSERT INTO zugh.users (age, score) VALUES (16, 7)\n>>> q2 = tb.where().select()\n>>> print(q2)\nSELECT * FROM zugh.users\n>>> q2.exe() # execute q2\n((), 0)\n>>> q1.exe() # execute q1\n1\n>>> q2.exe() # execute q2 again\n(((1, 16, 7),), 1)\n```\n\n### Insert Ignore\n\n```py\n>>> q3 = tb.insert_ignore(id=1, age=16, score=7)\n>>> print(q3)\nINSERT IGNORE INTO zugh.users (id, age, score) VALUES (1, 16, 7)\n>>> q3.exe() # would show a duplicate key warning\n```\n\n### Insert Or Update\n\nYou can use `F` object or `values` object to complete complex query.\n\n```py\nfrom zugh.query.others import F, values\n>>> row = dict(id=1, age=16, score=7)\n>>> q4 = tb.upsert(row, dict(age=9))\n>>> print(q4)\nINSERT INTO zugh.users (id, age, score) VALUES (1, 16, 7) ON DUPLICATE KEY UPDATE age = 9\n>>> update_fv = dict(age=F('age')-1, score=values('age')+1)\n>>> q5 = tb.upsert(row, update_fv=update_fv)\n>>> print(q5)\nINSERT INTO zugh.users (id, age, score) VALUES (1, 16, 7) ON DUPLICATE KEY UPDATE age = age - 1, score = VALUES(age) + 1\n```\n\n### Insert Multi Rows\n\n```py\n>>> rows = [\n dict(age=9, score=8),\n dict(age=7, score=9),\n dict(age=17, score=7),\n dict(age=23, score=7),\n ]\n\n>>> q6 = tb.insert_multi(rows)\n>>> print(q6)\nINSERT INTO zugh.users (age, score) VALUES (9, 8), (7, 9), (17, 7), (23, 7)\n>>> q6.exe() # execute q6\n4\n>>> q2.exe()\n(((1, 16, 7), (2, 9, 8), (3, 7, 9), (4, 17, 7), (5, 23, 7)), 5)\n```\n\n## Select\n\n```py\n>>> q7 = tb.where(id=3).select()\n>>> print(q7)\nSELECT * FROM zugh.users WHERE id = 3\n>>> q7.exe()\n(((3, 7, 9),), 1)\n>>> q8 = tb.where().select('id', 'age')\n>>> print(q8)\nSELECT id, age FROM zugh.users\n>>> q8.exe()\n(((1, 16), (2, 9), (3, 7), (4, 17), (5, 23)), 5)\n```\n\nBy default, when call `.exe()` of Select Query, it will return a `tuple` which contain queryset and number of rows.\nThe format of queryset format is still `tuple`. If you prefer a dict-format queryset, you should pass a parameter `cursorclass=pymysql.cursors.DictCursor` to configure connection. For more infomation, please refer docs of `PyMySQL`.\n\n### Filter\n\nthe `Table.where()` method of Table instance act as a filter.\n\nIf don't need to filter table, You can call `Table.select()` directly. It is a shortup of `Table.where().select()`.\n\n#### Logic Express\n\n```py\n>>> from zugh.query.logic import AND, OR, L\n>>> q9 = tb.where('id>3', 'id<7').select()\n>>> print(q9)\nSELECT * FROM zugh.users WHERE id>3 AND id<7\n>>> q9.exe()\n(((4, 17, 7), (5, 23, 7)), 2)\n\n>>> w1 = L('id<3')|L('id>7') # equal to OR('id<3', 'id>7')\n>>> w2 = OR('id<3', 'id>7')\n>>> w1\nOR(L(id<3),L(id>7))\n>>> w2\nOR(L(id<3),L(id>7))\n>>> print(w1)\nid<3 OR id>7\n>>> print(w2)\nid<3 OR id>7\n\n>>> q10 = tb.where(w2).select()\n>>> print(q10)\nSELECT * FROM zugh.users WHERE (id<3 OR id>7)\n>>> q10.exe()\n(((1, 16, 7), (2, 9, 8)), 2)\n\n# you can combine complex Logic object use L, OR and AND\n\n>>> w3 = L('id>3') & L('id<7') # equal to AND('id>3', 'id<7')\n>>> print(w3)\nid>3 AND id<7\n>>> w4 = L('age>3') & L('age<20')\n>>> print(w4)\nage>3 AND age<20\n\n>>> print(OR(w3, w4))\n(id>3 AND id<7) OR (age>3 AND age<20)\n>>> print(w3|w4)\n(id>3 AND id<7) OR (age>3 AND age<20)\n```\n\n#### Compare\n\nWe use class or their instance to deal with compare express.You can find them in `zugh.query.condition` module.\n\n| SQL Operator | Python Class/Instance/Operator | example |\n| ------------ | ------------------------------ | ---------------------------- |\n| = | `eq`, `=` | .where(name='lisa') |\n| != | `ne` | .where(name=ne('lisa')) |\n| > | `gt` | .where(amount=gt(9)) |\n| >= | `ge` | .where(amount=ge(9)) |\n| < | `lt` | .where(amount=lt(6)) |\n| <= | `le` | .where(amount=le(5)) |\n| IN | `In` | .where(id=In(1,2,3,4)) |\n| NOT IN | `NIn` | .where(id=NIn(98,34,2)) |\n| LIKE | `like` | .where(name=like('lisa%')) |\n| NOT LIKE | `unlike` | .where(name=unlike('john%')) |\n| IS NULL | `NULL` | .where(age=NULL) |\n| IS NOT NULL | `NOT_NULL` | .where(age=NOT_NULL) |\n\nThough works, `eq` is meaningless. For convenience, you would always use `=` .\n\n```py\n>>> from zugh.query.condition import NOT_NULL, NULL, In, NIn, ge, gt, le, like, lt, ne, unlike\n>>> q11 = tb.where(id=gt(3)).select()\n>>> print(q11)\nSELECT * FROM zugh.users WHERE id > 3\n\n>>> q12 = tb.where(id=gt(3), age=lt(18)).select()\n>>> print(q12)\nSELECT * FROM zugh.users WHERE id > 3 AND age < 18\n>>> q12.exe()\n(((4, 17, 7),), 1)\n\n>>> q13 = tb.where(id=In(1,3,5,7,9)).select('id', 'score')\n>>> print(q13)\nSELECT id, score FROM zugh.users WHERE id IN (1,3,5,7,9)\n>>> q13.exe()\n(((1, 7), (3, 9), (5, 7)), 3)\n\n>>> q14 = tb.where(score=NULL).select()\n>>> print(q14)\nSELECT * FROM zugh.users WHERE score IS NULL\n```\n\n### Alias\n\n```py\n>>> from zugh.query.others import As\n>>> from zugh.query.aggregate import Max\n>>> qa = tb.where().select(max_age=Max('age'))\n>>> print(qa)\nSELECT max(age) AS max_age FROM zugh.users\n>>> print(tb.where().select(As(Max('age'), 'max_age')))\nSELECT max(age) AS max_age FROM zugh.users\n```\n\nWe support alias, but the default `cursorclass` of PyMySQL will return query set in tuple. In this case, alias is useless.\nIf you want to return dict, you need to configure connection parameter `cursorclass=pymysql.cursors.DictCursor`. For more information, please refer PyMySQL's documents.\n\n### Sort\n\n```py\n>>> q15 = tb.where().select().order_by('age')\n>>> print(q15)\nSELECT * FROM zugh.users ORDER BY age\n>>> q15.exe()\n(((3, 7, 9), (2, 9, 8), (1, 16, 7), (4, 17, 7), (5, 23, 7)), 5)\n\n# You can use prefix '-' to sort reverse.\n>>> q16 = tb.where().select().order_by('-age', 'score')\n>>> print(q16)\nSELECT * FROM zugh.users ORDER BY age DESC, score\n>>> q16.exe()\n(((5, 23, 7), (4, 17, 7), (1, 16, 7), (2, 9, 8), (3, 7, 9)), 5)\n```\n\n### Limit\n\nWe use a magical `slice` to act limit/offset, `Select Query`' slice will return a instance of\n`zugh.query.core.Limit`, which is a subclass of `zugh.query.core.SelectBase`.\n\n```py\n>>> qm = tb.where().select()\n>>> qm1 = qm[:3] # fetch frist three\n>>> print(qm1)\nSELECT * FROM zugh.users LIMIT 3\n>>> qm2 = qm[2:4]\n>>> print(qm2)\nSELECT * FROM zugh.users LIMIT 2, 2\n>>> qm3 = qm[2:]\n>>> print(qm3)\nSELECT * FROM zugh.users LIMIT 2, 18446744073709551614\n```\n\nExcept for instances of `Limit`, all the others instance of `SelectBase` could use slice to return a instance of `Limit`.\n\nThe slice here don't accept negative numbers.\n\n### Aggregate\n\nWe provide some aggregation functions in `zugh.query.aggregate` moulde.\n\n```py\n>>> from zugh.query.aggregate import Avg, Count, Max, Min, Sum\n>>> q17 = tb.where().select(Avg('age'))\n>>> print(q17)\nSELECT avg(age) FROM zugh.users\n>>> q17.exe()\n(((Decimal('14.4000'),),), 1)\n\n>>> q18 = tb.where().select('score', Count('id')).group_by('score')\n>>> print(q18)\nSELECT score, count(id) FROM zugh.users GROUP BY score\n>>> q18.exe()\n(((7, 3), (8, 1), (9, 1)), 3)\n```\n\nYou can also use write 'raw' functions as long as you like it, such as:\n\n```py\nq17 = tb.where().select('avg(age)')\n```\n\n### Distinct\n\n```py\nfrom zugh.query.others import distinct\n>>> q19 = tb.where().select('distinct age', 'score')\n>>> print(q15)\nSELECT distinct age, score FROM zugh.users\n>>> q19.exe()\n(((16, 7), (9, 8), (7, 9), (17, 7), (23, 7)), 5)\n\n>>> q20 = tb.where().select(distinct('age'), 'score')\n>>> print(q20)\nSELECT DISTINCT age, score FROM zugh.users\n\n>>> q21 = tb.where().select(Count(distinct('age')))\n>>> print(q21)\nSELECT count(DISTINCT age) FROM zugh.users\n```\n\n### Subquery\n\nAt present, `In` and `NIn` express support subquery, it could accept a instance of `SelectBase` as a parameter.But they can not accept instance of `zugh.schema.table.TempTable` as a parameter.\n\n`TempTable` accept a `Select Query` as frist parameter and a alias string as second parameter. It would act like a normal read-only table, you could join it with others, or query data as a new instance `TempTable`.\n\n```py\n>>> from zugh.schema.table import TempTable\n>>> q22 = tb.where().select(Max('age'))\n>>> print(q22)\nSELECT max(age) FROM zugh.users\n\n>>> q23 = tb.where(age=In(q22)).select()\n>>> print(q23)\nSELECT * FROM zugh.users WHERE age IN (SELECT max(age) FROM zugh.users)\n>>> q23.exe()\n(((5, 23, 7),), 1)\n\n>>> q_t = tb.where(id=gt(2)).select()\n>>> tb_t1 = q_t.as_table('ak') # equal to tb_t1 = TempTable(q_t, 'ak')\n>>> tb_t1\nTempTable(SELECT * FROM zugh.users WHERE id > 2)\n>>>\n>>> sql2 = \"\"\"\nCREATE TABLE zugh.account (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `user_id` int(11) NOT NULL,\n `amount` decimal(11,2) NOT NULL DEFAULT '0',\n PRIMARY KEY (`id`)\n) Engine=InnoDB DEFAULT CHARSET=utf8mb4;\n\"\"\"\n>>> with conn as cn:\n with cn.cursor() as cursor:\n cursor.execute(sql2)\n>>> tb2 = Table('account', db, alias='a') # If tend to join table, alias is necessary\n>>> rows2 = (\n dict(user_id=1, amount='99.89'),\n dict(user_id=2, amount='292.2'),\n dict(user_id=3, amount='299.89'),\n dict(user_id=4, amount='192.1'),\n dict(user_id=5, amount='183.7'),\n)\n>>> tb2.insert_multi(rows2).exe()\n>>>\n>>> tb_t2 = tb_t1.inner_join(tb2, on='a.user_id = ak.id')\n>>> q_t2 = tb_t2.select()\n>>> print(q_t2)\nSELECT * FROM (SELECT * FROM zugh.users WHERE id > 2) AS ak INNER JOIN zugh.account AS a ON a.user_id = ak.id\n>>> q_t2.exe()\n(((3, 7, 8, 3, 3, Decimal('299.89')), (4, 17, 8, 4, 4, Decimal('192.10'))), 2)\n```\n\n### Join In\n\nLet's add a new table and query from the join table:\n\n```py\n>>> tb3 = Table('users', db, alias='b') # If tend to join table, alias is necessary\n>>> tb_i = tb2.inner_join(tb3, on='a.user_id=b.id')\n>>> q_i = tb_i.where(a__id=gt(2)).select('a.id', 'a.user_id', 'a.amount', 'b.score', 'b.age')\n>>> print(q_i)\nSELECT a.id, a.user_id, a.amount, b.score, b.age FROM zugh.account AS a INNER JOIN zugh.users AS b ON a.user_id=b.id WHERE a.id > 2\n>>> q_i.exe()\n(((3, 3, Decimal('299.89'), 8, 7), (4, 4, Decimal('192.10'), 8, 17)), 2)\n```\n\nIf two underscores are used in the keyword of `where` method, the two underscores will be replaced by solid point.\nFor example, `a__id` will be replaced with `a.id`. This idea was copied from the Django project.\n\nWe provide `Table.inner_join()`, `Table.left_join()` and `Table.right_join()` methods to support Table join.\n\n### Union\n\nYou can call `union` or `union_all` method from `Select object`. For example:\n\n```py\n>>> from zugh.query.condition import gt, lt\n>>> q_u1 = tb.where(id=lt(5)).select()\n>>> q_u2 = tb.where(age=gt(20)).select()\n>>> q_u = q_u1.union_all(q_u2)\n>>> print(q_u)\nSELECT * FROM zugh.users WHERE id < 5 UNION ALL SELECT * FROM zugh.users WHERE age > 20\n```\n\n## Update\n\n```py\n>>> tb.where(id=1).select().exe()\n(((1, 16, 7),), 1)\n>>> q24 = tb.where(id=1).update(age=28)\n>>> print(q24)\nUPDATE zugh.users SET age = 28 WHERE id = 1\n>>> q24.exe()\n1\n>>> tb.where(id=1).select().exe()\n(((1, 28, 7),), 1)\n```\n\n### F Object\n\nF object means that it is a field, not a string. It could be used in `Table.where()` or `Where.update()`.\nF class is a subclass of `ArithmeticBase`. So F objects can perform mathematical operations, and it will return a new `ArithmeticBase` instance.\n\nSee the following examples:\n\n```py\n>>> from zugh.query.others import F\n>>> tb.where(id=1).select().exe()\n(((1, 28, 7),), 1)\n\n>>> q25 = tb.where(id=1).update(age=F('age') - 2, score=F('score') + 6)\n>>> print(q25)\nUPDATE zugh.users SET age = age - 2, score = score + 6 WHERE id = 1\n>>> q25.exe()\n1\n>>> tb.where(id=1).select().exe()\n(((1, 26, 13),), 1)\n\n# F object also use in filter\n>>> q26 = tb.where(score=gt(F('age')*2)).select()\n>>> print(q26)\nSELECT * FROM zugh.users WHERE score > age * 2\n>>> q26.exe()\n((), 0)\n```\n\n## Delete\n\n```py\n>>> tb.where(id=5).select().exe()\n(((5, 23, 7),), 1)\n>>> q23 = tb.where(id=5).delete()\n>>> print(q23)\nDELETE FROM zugh.users WHERE id = 5\n>>> q23.exe()\n1\n>>> tb.where(id=5).select().exe()\n((), 0)\n```\n\n## Decorator\n\n### db.query.query\n\n`query` decorator wraps a function which return a Query object. When call the wrapped function,\nit would execute a Query object. For example:\n\n```py\n>>> from zugh.query.aggregate import Max\n>>> from zugh.db.query import query\n\n>>> @query()\n def query_max_score():\n q1 = tb.where().select(Max('score'))\n q2 = tb.where(score=In(q1)).select()\n return q2\n\n>>> query_max_score()\n(((1, 26, 13),), 1)\n```\n\n`query` accept 2 parameters: `conn_config` and `conn_pool`. If the Query object returned don't\nconfigure connection, you can pass a `conn_config` dict or a connection pool to it.\n\n### db.query.transaction\n\n`transaction` decorator wrap a function which return a list of `Query ojbect`. When call the wrapped\nfunction, it would execute the list of `Query object` as a transaction. If transaction succeed, return True, otherwise return False.\n\nFor example:\n\n```py\nfrom zugh.db.query import transaction\nfrom zugh.query.others import F\n\n@transaction(conn_pool=pool)\ndef mv_score():\n q1 = tb.where(id=3).update(score=F('score') - 1)\n q2 = tb.where(id=4).update(score=F('score') + 1)\n return (q1, q2)\n\n>>> mv_score()\nTrue\n```\n\n## String\n\nSome string function awailable in `zugh.query.string` module.\n\nconcat 2 field:\n\n```py\nfrom zugh.query.string import Concat, S, Substring\n>>> q24 = tb.where().select(Concat('age', 'score'))\n>>> print(q24)\nSELECT concat(age, score) FROM zugh.users\n```\n\n### S Object\n\nIn string functions, str meaning field name instead of string. You should use `S object` to represent string.\n\n```py\nfrom zugh.query.string import Concat, S, Substring\n>>> q25 = tb.where().select(Concat(S('PRI-'), 'age'))\n>>> print(q25)\nSELECT concat('PRI-', age) FROM zugh.users\n>>> q25.exe()\n((('PRI-26',), ('PRI-9',), ('PRI-7',), ('PRI-17',)), 4)\n\n>>> print(tb.where().select(Substring('age', 2)))\nSELECT substring(age, 2) FROM zugh.users\n```\n\n## Math\n\nSome math functions are awailable in `zugh.query.math` module.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/StephenKwen/zugh", "keywords": "SQL,MySQL", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "zugh", "package_url": "https://pypi.org/project/zugh/", "platform": "", "project_url": "https://pypi.org/project/zugh/", "project_urls": { "Homepage": "https://github.com/StephenKwen/zugh" }, "release_url": "https://pypi.org/project/zugh/0.0.1b3/", "requires_dist": [ "PyMySQL (>=0.9.3)" ], "requires_python": ">=3.6", "summary": "Access to database flexibly", "version": "0.0.1b3" }, "last_serial": 5612716, "releases": { "0.0.1b2": [ { "comment_text": "", "digests": { "md5": "b196547d89f50d3ec67e4f7bb7e44a50", "sha256": "fc97ef9b452a5e143181e9d7bcad8ad0457f5fa1ac82ad2f15df4e00b5ee66f7" }, "downloads": -1, "filename": "zugh-0.0.1b2-py3-none-any.whl", "has_sig": false, "md5_digest": "b196547d89f50d3ec67e4f7bb7e44a50", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 19461, "upload_time": "2019-07-29T08:39:39", "url": "https://files.pythonhosted.org/packages/bb/54/032066ecde760b73a18f1131f442122394caa007918baebd62ef5507eabb/zugh-0.0.1b2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a1c857f9b72c865ebd49764464824e31", "sha256": "57d31ea2f5fea2129ff57814f4dcb664bf1fa283dbf24291779b0afab95e2ead" }, "downloads": -1, "filename": "zugh-0.0.1b2.tar.gz", "has_sig": false, "md5_digest": "a1c857f9b72c865ebd49764464824e31", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 20627, "upload_time": "2019-07-29T08:39:42", "url": "https://files.pythonhosted.org/packages/f0/d9/2b880ab730340068178bef768ed90e519e4978a794ff41e3177f23f25d4e/zugh-0.0.1b2.tar.gz" } ], "0.0.1b3": [ { "comment_text": "", "digests": { "md5": "cace88de3076797c31381280d1245f66", "sha256": "71596677773c280c09d452ea281311bcd4fb78344a3c57b12c6f6248f12de2d9" }, "downloads": -1, "filename": "zugh-0.0.1b3-py3-none-any.whl", "has_sig": false, "md5_digest": "cace88de3076797c31381280d1245f66", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 19463, "upload_time": "2019-07-31T10:33:30", "url": "https://files.pythonhosted.org/packages/b9/3a/75b39adc4bc3c7284ac5f1c833ded2db01bc5dea8999ea834d6bfdb30e71/zugh-0.0.1b3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e96b5a262a09523e69a8f36edf341fe2", "sha256": "84d9c5c02c6e12eac30cabab88f651b9d05bcc514d001d5c792a37b91607fb66" }, "downloads": -1, "filename": "zugh-0.0.1b3.tar.gz", "has_sig": false, "md5_digest": "e96b5a262a09523e69a8f36edf341fe2", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 20648, "upload_time": "2019-07-31T10:33:32", "url": "https://files.pythonhosted.org/packages/97/48/6834ed6276036ef79928cac8e48cd3370550019d7877c117de2a1df349d3/zugh-0.0.1b3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "cace88de3076797c31381280d1245f66", "sha256": "71596677773c280c09d452ea281311bcd4fb78344a3c57b12c6f6248f12de2d9" }, "downloads": -1, "filename": "zugh-0.0.1b3-py3-none-any.whl", "has_sig": false, "md5_digest": "cace88de3076797c31381280d1245f66", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 19463, "upload_time": "2019-07-31T10:33:30", "url": "https://files.pythonhosted.org/packages/b9/3a/75b39adc4bc3c7284ac5f1c833ded2db01bc5dea8999ea834d6bfdb30e71/zugh-0.0.1b3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e96b5a262a09523e69a8f36edf341fe2", "sha256": "84d9c5c02c6e12eac30cabab88f651b9d05bcc514d001d5c792a37b91607fb66" }, "downloads": -1, "filename": "zugh-0.0.1b3.tar.gz", "has_sig": false, "md5_digest": "e96b5a262a09523e69a8f36edf341fe2", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 20648, "upload_time": "2019-07-31T10:33:32", "url": "https://files.pythonhosted.org/packages/97/48/6834ed6276036ef79928cac8e48cd3370550019d7877c117de2a1df349d3/zugh-0.0.1b3.tar.gz" } ] }