├── images ├── demo.png ├── result.png ├── deliver_here.png ├── click_add_button.png ├── update_location.png └── payload_behrouz_biryani.png ├── swiggy_order ├── utils.py ├── constants.py ├── __init__.py └── apis │ └── __init__.py ├── pyproject.toml ├── config.json ├── LICENCE.md ├── .gitignore ├── README.md └── poetry.lock /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ltbringer/swiggy-order/HEAD/images/demo.png -------------------------------------------------------------------------------- /images/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ltbringer/swiggy-order/HEAD/images/result.png -------------------------------------------------------------------------------- /images/deliver_here.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ltbringer/swiggy-order/HEAD/images/deliver_here.png -------------------------------------------------------------------------------- /images/click_add_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ltbringer/swiggy-order/HEAD/images/click_add_button.png -------------------------------------------------------------------------------- /images/update_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ltbringer/swiggy-order/HEAD/images/update_location.png -------------------------------------------------------------------------------- /images/payload_behrouz_biryani.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ltbringer/swiggy-order/HEAD/images/payload_behrouz_biryani.png -------------------------------------------------------------------------------- /swiggy_order/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup coloredlogs 3 | """ 4 | import logging 5 | import coloredlogs 6 | from typing import Union 7 | 8 | 9 | log = logging.getLogger("swiggy-order") 10 | fmt = "%(asctime)s:%(msecs)03d %(name)s [%(filename)s:%(lineno)s] %(levelname)s %(message)s" 11 | coloredlogs.install(level=logging.INFO, logger=log, fmt=fmt) 12 | 13 | 14 | def change_log_level(level: str) -> None: 15 | log.setLevel(level) 16 | for handler in log.handlers: 17 | handler.setLevel(level) 18 | -------------------------------------------------------------------------------- /swiggy_order/constants.py: -------------------------------------------------------------------------------- 1 | SWIGGY_URL = "https://www.swiggy.com" 2 | CSRF_PATTERN = r"window._csrfToken = \"(.+?)\";" 3 | SWIGGY_COOKIE = "__SW" 4 | SWIGGY_LOGIN_URL = f"{SWIGGY_URL}/dapi/auth/signin-with-check" 5 | SWIGGY_SEND_OTP_URL = f"{SWIGGY_URL}/dapi/auth/sms-otp" 6 | SWIGGY_VERIFY_OTP_URL = f"{SWIGGY_URL}/dapi/auth/otp-verify" 7 | STATUS_FLAG = "statusCode" 8 | STATUS_MESSAGE = "statusMessage" 9 | CART_URL = f"{SWIGGY_URL}/dapi/cart" 10 | APPLY_COUPON_URL = f"{SWIGGY_URL}/dapi/cart/applyCoupon" 11 | PLACE_ORDER_URL = f"{SWIGGY_URL}/dapi/order/place-v2" 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "swiggy-order" 3 | version = "1.0.0" 4 | description = "Order food via terminal." 5 | authors = ["Amresh Venugopal "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | requests = "^2.25.1" 11 | docopt = "^0.6.2" 12 | coloredlogs = "^15.0" 13 | 14 | [tool.poetry.dev-dependencies] 15 | ipython = "^7.19.0" 16 | black = {version = "^20.8b1", allow-prereleases = true} 17 | 18 | [tool.poetry.scripts] 19 | order-food = 'swiggy_order:main' 20 | [build-system] 21 | requires = ["poetry>=0.12"] 22 | build-backend = "poetry.masonry.api" 23 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "registered_phone": "", 3 | "address_id": "", 4 | "menu": [ 5 | { 6 | "name": "New york Cheesecake", 7 | "comment": "This would work for you if you live near me. BTW comments are not required at all 😉.", 8 | "payload": { 9 | "flushFirst": true, 10 | "cart": { 11 | "restaurantId": 35849, 12 | "address_id": "", 13 | "couponCode": "", 14 | "cartItems": [ 15 | { 16 | "addons": [], 17 | "variants": [], 18 | "menu_item_id": 53335198, 19 | "quantity": 1 20 | } 21 | ], 22 | "mealItems": [], 23 | "subscriptionItems": [] 24 | }, 25 | "_csrf": "Iw4JkduEKJeo-l1OuyJ-jtAoDecFMXXb9suuliIY" 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /swiggy_order/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Order food through terminal. 3 | 4 | Usage: 5 | order-food --config= [--coupon-code=] [--log-level=] 6 | order-food (-h | --help) 7 | order-food --version 8 | 9 | Options: 10 | -h --help Show this screen. 11 | --version Show version. 12 | --config= Path to config file (.json is expected). 13 | --log-level= Log level [default: INFO]. 14 | --coupon-code= Discount offers [default: ]. 15 | """ 16 | import os 17 | import json 18 | import time 19 | 20 | from docopt import docopt 21 | 22 | from swiggy_order.apis import login, update_cart, apply_coupon_code, place_order 23 | from swiggy_order.utils import log, change_log_level 24 | 25 | 26 | def extract_if_valid(config_file, config, property): 27 | try: 28 | return config[property] 29 | except KeyError: 30 | KeyError(f"Expected key `registered_phone` within {config_file}.") 31 | 32 | def order_food(config_file, config): 33 | menu = extract_if_valid(config_file, config, "menu") 34 | registered_phone = extract_if_valid(config_file, config, "registered_phone") 35 | address_id = extract_if_valid(config_file, config, "address_id") 36 | choice = None 37 | item_index = -1 38 | menu_items = [f'{i + 1}) {item["name"]}' for i, item in enumerate(menu)] 39 | 40 | while not choice: 41 | log.info("\n\t\tMENU\n\n%s", "\n".join(menu_items)) 42 | choice = input("Enter item id: ") 43 | 44 | if not choice.isdigit(): 45 | choice = None 46 | continue 47 | 48 | item_index = int(choice) - 1 49 | if item_index > len(menu_items): 50 | choice = None 51 | 52 | selected_items = menu[item_index]["payload"] 53 | return registered_phone, address_id, selected_items 54 | 55 | 56 | def main(): 57 | args = docopt(__doc__) 58 | config_file = args["--config"] 59 | log_level = args["--log-level"] 60 | coupon_code = args["--coupon-code"] 61 | 62 | if log_level: 63 | change_log_level(log_level) 64 | 65 | with open(config_file, "r") as handle: 66 | config = json.load(handle) 67 | 68 | registered_phone, address_id, all_items = order_food(config_file, config) 69 | 70 | login(registered_phone) 71 | log.info("Setting items in cart.") 72 | update_cart(all_items) 73 | time.sleep(1.5) 74 | log.info("Applying coupon code='%s'", coupon_code) 75 | apply_coupon_code(coupon_code=coupon_code) 76 | time.sleep(1.5) 77 | place_order("SwiggyPay", address_id) 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | .vscode/ 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | *.json -------------------------------------------------------------------------------- /swiggy_order/apis/__init__.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pprint import pformat 3 | 4 | import requests 5 | 6 | from swiggy_order.constants import ( 7 | SWIGGY_URL, 8 | CSRF_PATTERN, 9 | SWIGGY_COOKIE, 10 | SWIGGY_SEND_OTP_URL, 11 | SWIGGY_VERIFY_OTP_URL, 12 | STATUS_FLAG, 13 | STATUS_MESSAGE, 14 | CART_URL, 15 | APPLY_COUPON_URL, 16 | PLACE_ORDER_URL, 17 | ) 18 | from swiggy_order.utils import log 19 | 20 | 21 | session = requests.Session() 22 | csrf_source_pattern = re.compile(CSRF_PATTERN) 23 | 24 | 25 | def get_cookie(cookies, name): 26 | return cookies.get_dict().get(name) 27 | 28 | 29 | def validate_response(response): 30 | try: 31 | if response.json().get(STATUS_FLAG) != 0: 32 | log.error(response.json().get(STATUS_MESSAGE)) 33 | raise ValueError(f"Non-zero {STATUS_FLAG}!") 34 | except AttributeError: 35 | log.error(response.text) 36 | raise ValueError(response.text) 37 | 38 | 39 | def get_otp(registered_phone, sw_cookie, csrf_token): 40 | return session.post( 41 | SWIGGY_SEND_OTP_URL, 42 | headers={ 43 | "content-type": "application/json", 44 | "Cookie": "__SW={}".format(sw_cookie), 45 | "User-Agent": "Mozilla/Gecko/Firefox/65.0", 46 | }, 47 | json={"mobile": registered_phone, "_csrf": csrf_token}, 48 | ) 49 | 50 | 51 | def verify_otp(otp, csrf_token): 52 | return session.post( 53 | SWIGGY_VERIFY_OTP_URL, 54 | headers={ 55 | "content-type": "application/json", 56 | "User-Agent": "Mozilla/Gecko/Firefox/65.0", 57 | }, 58 | json={"otp": otp, "_csrf": csrf_token}, 59 | ) 60 | 61 | 62 | def make_connection(): 63 | response = session.get(SWIGGY_URL) 64 | try: 65 | csrf_token = csrf_source_pattern.search(response.text).group(1) 66 | sw_cookie = get_cookie(response.cookies, SWIGGY_COOKIE) 67 | return sw_cookie, csrf_token 68 | except IndexError: 69 | raise IndexError( 70 | f"Pattern={CSRF_PATTERN} matched but csrf token not found in expected location." 71 | ) 72 | except TypeError: 73 | raise TypeError( 74 | f"Expected response.txt to be str but found {type(response.text)} instead." 75 | ) 76 | 77 | 78 | def login(registered_phone): 79 | sw_cookie, csrf_token = make_connection() 80 | otp_response = get_otp(registered_phone, sw_cookie, csrf_token) 81 | 82 | if otp_response.json().get(STATUS_FLAG) != 0: 83 | raise ValueError(otp_response.text) 84 | 85 | sw_cookie, csrf_token = make_connection() 86 | otp = input("Enter OTP: ") 87 | 88 | response = verify_otp(otp, csrf_token) 89 | validate_response(response) 90 | log.debug(pformat(response.json())) 91 | 92 | 93 | def update_cart(payload, quantity=1): 94 | _, csrf_token = make_connection() 95 | payload["_csrf"] = csrf_token 96 | payload["cart"]["cartItems"][0]["quantity"] = quantity 97 | response = session.post(CART_URL, json=payload) 98 | validate_response(response) 99 | log.debug(pformat(response.json())) 100 | 101 | 102 | def apply_coupon_code(coupon_code=""): 103 | if not coupon_code: 104 | return 105 | _, csrf_token = make_connection() 106 | payload = {"couponCode": coupon_code, "_csrf": csrf_token} 107 | response = session.post(APPLY_COUPON_URL, json=payload) 108 | validate_response(response) 109 | log.debug(pformat(response.json())) 110 | 111 | 112 | def place_order(payment_method, address_id): 113 | _, csrf_token = make_connection() 114 | payload = { 115 | "order": { 116 | "payment_cod_method": payment_method, 117 | "address_id": str(address_id), 118 | "order_comments": "", 119 | "force_validate_coupon": True, 120 | }, 121 | "_csrf": csrf_token, 122 | } 123 | 124 | response = session.post(PLACE_ORDER_URL, json=payload) 125 | validate_response(response) 126 | log.debug(pformat(response.json())) 127 | log.info("Order placed ✨🌟 🥘🥙🥗 🌟✨ !") 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiggy-order 2 | 3 | I can't cook. It is a craft that needs patience, creativity and time. I am very short on time and no amount of youtube training yields the same taste that money can buy. That clears a part of this project's goal - _buying food_. 4 | 5 | _but why write code? aren't there apps already working hard for it?_ 6 | Yes, they are working too hard and they are damn good at their job. I am trying to keep myself fit since 2nd November 2019, a day when I woke up with a pain so sharp in my back that I felt I wouldn't stand up again. Luckily, it wasn't that bad but it hurt for weeks. I promised myself never again. That meant quitting a lifestyle of having desserts 3X a day and not working out at all. 7 | 8 | I started training for MMA and that somehow took care of my sugar cravings. I could easily avoid desserts and feel nothing, no cravings, no regrets. 23rd March 2020, India annouced a lockdown to prevent the spread of covid19. This meant I had to train at home and that wasn't supposed to work (No sparring no fun). I noticed I was getting tempted every once in a while and ordering slices of cakes with ice creams and shakes. I knew where this was headed. 9 | 10 | I have tried uninstalling swiggy and in no more than 3 days I install it back. If I could tell the app to not recommend certain items, I could make that work too. Since that isn't the case. I made this. If this helps you; continue using it, you owe me nothing. It is just a bunch of API integrations. 11 | 12 | ## Thought Process 13 | - I decided to keep a list of my healthy buys and set this project to protect myself from my gluttony. 14 | - Integrate apis so that ordering food can happen via terminal and I don't have to browse. An additional plus is browsing can take 10 - 15 minutes without bringing any difference in my choices. 15 | - Set up Swiggy money with a monthly quota so that I don't burn through my pocket. 16 | 17 | ## Installation and Usage 18 | ``` 19 | pip install swiggy-order 20 | order-food --config=/path/to/config.json [--coupon-code=] [--log-level=DEBUG] 21 | ``` 22 | 23 | ## Config 24 | A very minimal `config.json` example: 25 | ```python 26 | { 27 | "registered_phone":"<10-digit-phone-number>", 28 | "address_id":"", 29 | "menu":[ 30 | { 31 | "name":"", 32 | "payload":{...} 33 | }] 34 | } 35 | ``` 36 | There is a [config.json](./config.json) provided with this code for reference, and if you live near me you can get that cheesecake delivered 😄. 37 | 38 | ### Payload 39 | An example payload looks like this: 40 | ```python 41 | { 42 | "flushFirst":0, 43 | "cart":{ 44 | "restaurantId":"", 45 | "address_id":"", 46 | "couponCode": "", 47 | "cartItems":[ 48 | { 49 | "addons":[ 50 | 51 | ], 52 | "variants":[ 53 | 54 | ], 55 | "menu_item_id":"", 56 | "quantity":"" 57 | } 58 | ], 59 | "mealItems":[ 60 | 61 | ], 62 | "subscriptionItems":[ 63 | 64 | ] 65 | }, 66 | "_csrf": "" # this is managed by this project, you can leave it empty or let it have its value when copied. The project will set it right. 67 | } 68 | } 69 | ``` 70 | This can be obtained via opening the network tab and monitoring the response for the `POST` requests on `https://www.swiggy.com/dapi/cart` API, when an item is selected from the menu and it shows up in the cart. The reason for copying the payload is to prevent code changes if the API structure / restaurant-ids / menu-item-ids / ??? were to change. The open sourced nature of this tool means fixing such issues is a matter of PRs but it didn't occur to me as time well spent. None of the changes I can forsee would be frequent, so changing the `config.json` seems to be much more time efficient than changing the code. 71 | 72 | ![Payload appears after clicking the ADD button](./images/click_add_button.png) 73 | 74 | Payload appears after clicking the ADD button 75 | 76 | ![payload for behrouz biryani](./images/payload_behrouz_biryani.png) 77 | Payload for adding this dish from behrouz biryani to your cart 🍛. You can add an entry in `config.json` as: 78 | 79 | ```json 80 | { 81 | "menu": [{ 82 | "name": "biryani boneless", 83 | "payload": "" 84 | }] 85 | } 86 | ``` 87 | Once added you can retain the items in a list. 88 | 89 | ### Address 90 | `address_id` can be found through the web-interface, monitor the response for the `POST` requests on `https://www.swiggy.com/dapi/cart` API when trying to click the "DELIVER HERE" button, post the checkout phase. 🗺 91 | 92 | ![Address updates after clicking the DELIVER HERE button](./images/deliver_here.png) 93 | 94 | Address updates after clicking the DELIVER HERE button 👆 95 | 96 | ![Look at updated address_id](./images/payload_behrouz_biryani.png) 97 | Notice the updated value of `address_id`. This can be kept within `config.json`, should be a one time addition. 98 | 99 | ### Payment Method 100 | Currently, this project assumes that payments are made via swiggy money only. I didn't want to solve for every payment method, those would anyway require callbacks to ensure safety. That is a lot of work and somehow there is a server someone needs to maintain. 💳💸 101 | 102 | ## Demo 103 | ![Demo](./images/demo.png) 104 | 105 | - Run the script with a coupon code. 106 | - Select item(s) to order. Do note I haven't written any code to exclude items from different outlets. 107 | - Provide the OTP from your registered mobile number. You would have to do this everytime, as long as this runs as a script. 108 | 109 | ![Result](./images/result.png) 110 | 111 | ## Fin 112 | Eat well my friends. It will show as you age, I can already feel the difference from my 20s and I am not even 30 yet. 113 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 4 | name = "appdirs" 5 | optional = false 6 | python-versions = "*" 7 | version = "1.4.4" 8 | 9 | [[package]] 10 | category = "dev" 11 | description = "Disable App Nap on macOS >= 10.9" 12 | marker = "sys_platform == \"darwin\"" 13 | name = "appnope" 14 | optional = false 15 | python-versions = "*" 16 | version = "0.1.2" 17 | 18 | [[package]] 19 | category = "dev" 20 | description = "Specifications for callback functions passed in to an API" 21 | name = "backcall" 22 | optional = false 23 | python-versions = "*" 24 | version = "0.2.0" 25 | 26 | [[package]] 27 | category = "dev" 28 | description = "The uncompromising code formatter." 29 | name = "black" 30 | optional = false 31 | python-versions = ">=3.6" 32 | version = "20.8b1" 33 | 34 | [package.dependencies] 35 | appdirs = "*" 36 | click = ">=7.1.2" 37 | mypy-extensions = ">=0.4.3" 38 | pathspec = ">=0.6,<1" 39 | regex = ">=2020.1.8" 40 | toml = ">=0.10.1" 41 | typed-ast = ">=1.4.0" 42 | typing-extensions = ">=3.7.4" 43 | 44 | [package.extras] 45 | colorama = ["colorama (>=0.4.3)"] 46 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 47 | 48 | [[package]] 49 | category = "main" 50 | description = "Python package for providing Mozilla's CA Bundle." 51 | name = "certifi" 52 | optional = false 53 | python-versions = "*" 54 | version = "2020.12.5" 55 | 56 | [[package]] 57 | category = "main" 58 | description = "Universal encoding detector for Python 2 and 3" 59 | name = "chardet" 60 | optional = false 61 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 62 | version = "4.0.0" 63 | 64 | [[package]] 65 | category = "dev" 66 | description = "Composable command line interface toolkit" 67 | name = "click" 68 | optional = false 69 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 70 | version = "7.1.2" 71 | 72 | [[package]] 73 | category = "dev" 74 | description = "Cross-platform colored terminal text." 75 | marker = "sys_platform == \"win32\"" 76 | name = "colorama" 77 | optional = false 78 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 79 | version = "0.4.4" 80 | 81 | [[package]] 82 | category = "main" 83 | description = "Colored terminal output for Python's logging module" 84 | name = "coloredlogs" 85 | optional = false 86 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 87 | version = "15.0" 88 | 89 | [package.dependencies] 90 | humanfriendly = ">=9.1" 91 | 92 | [package.extras] 93 | cron = ["capturer (>=2.4)"] 94 | 95 | [[package]] 96 | category = "dev" 97 | description = "Decorators for Humans" 98 | name = "decorator" 99 | optional = false 100 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 101 | version = "4.4.2" 102 | 103 | [[package]] 104 | category = "main" 105 | description = "Pythonic argument parser, that will make you smile" 106 | name = "docopt" 107 | optional = false 108 | python-versions = "*" 109 | version = "0.6.2" 110 | 111 | [[package]] 112 | category = "main" 113 | description = "Human friendly output for text interfaces using Python" 114 | name = "humanfriendly" 115 | optional = false 116 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 117 | version = "9.1" 118 | 119 | [package.dependencies] 120 | pyreadline = "*" 121 | 122 | [[package]] 123 | category = "main" 124 | description = "Internationalized Domain Names in Applications (IDNA)" 125 | name = "idna" 126 | optional = false 127 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 128 | version = "2.10" 129 | 130 | [[package]] 131 | category = "dev" 132 | description = "IPython: Productive Interactive Computing" 133 | name = "ipython" 134 | optional = false 135 | python-versions = ">=3.7" 136 | version = "7.19.0" 137 | 138 | [package.dependencies] 139 | appnope = "*" 140 | backcall = "*" 141 | colorama = "*" 142 | decorator = "*" 143 | jedi = ">=0.10" 144 | pexpect = ">4.3" 145 | pickleshare = "*" 146 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 147 | pygments = "*" 148 | setuptools = ">=18.5" 149 | traitlets = ">=4.2" 150 | 151 | [package.extras] 152 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] 153 | doc = ["Sphinx (>=1.3)"] 154 | kernel = ["ipykernel"] 155 | nbconvert = ["nbconvert"] 156 | nbformat = ["nbformat"] 157 | notebook = ["notebook", "ipywidgets"] 158 | parallel = ["ipyparallel"] 159 | qtconsole = ["qtconsole"] 160 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] 161 | 162 | [[package]] 163 | category = "dev" 164 | description = "Vestigial utilities from IPython" 165 | name = "ipython-genutils" 166 | optional = false 167 | python-versions = "*" 168 | version = "0.2.0" 169 | 170 | [[package]] 171 | category = "dev" 172 | description = "An autocompletion tool for Python that can be used for text editors." 173 | name = "jedi" 174 | optional = false 175 | python-versions = ">=3.6" 176 | version = "0.18.0" 177 | 178 | [package.dependencies] 179 | parso = ">=0.8.0,<0.9.0" 180 | 181 | [package.extras] 182 | qa = ["flake8 (3.8.3)", "mypy (0.782)"] 183 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] 184 | 185 | [[package]] 186 | category = "dev" 187 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 188 | name = "mypy-extensions" 189 | optional = false 190 | python-versions = "*" 191 | version = "0.4.3" 192 | 193 | [[package]] 194 | category = "dev" 195 | description = "A Python Parser" 196 | name = "parso" 197 | optional = false 198 | python-versions = ">=3.6" 199 | version = "0.8.1" 200 | 201 | [package.extras] 202 | qa = ["flake8 (3.8.3)", "mypy (0.782)"] 203 | testing = ["docopt", "pytest (<6.0.0)"] 204 | 205 | [[package]] 206 | category = "dev" 207 | description = "Utility library for gitignore style pattern matching of file paths." 208 | name = "pathspec" 209 | optional = false 210 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 211 | version = "0.8.1" 212 | 213 | [[package]] 214 | category = "dev" 215 | description = "Pexpect allows easy control of interactive console applications." 216 | marker = "sys_platform != \"win32\"" 217 | name = "pexpect" 218 | optional = false 219 | python-versions = "*" 220 | version = "4.8.0" 221 | 222 | [package.dependencies] 223 | ptyprocess = ">=0.5" 224 | 225 | [[package]] 226 | category = "dev" 227 | description = "Tiny 'shelve'-like database with concurrency support" 228 | name = "pickleshare" 229 | optional = false 230 | python-versions = "*" 231 | version = "0.7.5" 232 | 233 | [[package]] 234 | category = "dev" 235 | description = "Library for building powerful interactive command lines in Python" 236 | name = "prompt-toolkit" 237 | optional = false 238 | python-versions = ">=3.6.1" 239 | version = "3.0.8" 240 | 241 | [package.dependencies] 242 | wcwidth = "*" 243 | 244 | [[package]] 245 | category = "dev" 246 | description = "Run a subprocess in a pseudo terminal" 247 | marker = "sys_platform != \"win32\"" 248 | name = "ptyprocess" 249 | optional = false 250 | python-versions = "*" 251 | version = "0.6.0" 252 | 253 | [[package]] 254 | category = "dev" 255 | description = "Pygments is a syntax highlighting package written in Python." 256 | name = "pygments" 257 | optional = false 258 | python-versions = ">=3.5" 259 | version = "2.7.3" 260 | 261 | [[package]] 262 | category = "main" 263 | description = "A python implmementation of GNU readline." 264 | marker = "sys_platform == \"win32\"" 265 | name = "pyreadline" 266 | optional = false 267 | python-versions = "*" 268 | version = "2.1" 269 | 270 | [[package]] 271 | category = "dev" 272 | description = "Alternative regular expression module, to replace re." 273 | name = "regex" 274 | optional = false 275 | python-versions = "*" 276 | version = "2020.11.13" 277 | 278 | [[package]] 279 | category = "main" 280 | description = "Python HTTP for Humans." 281 | name = "requests" 282 | optional = false 283 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 284 | version = "2.25.1" 285 | 286 | [package.dependencies] 287 | certifi = ">=2017.4.17" 288 | chardet = ">=3.0.2,<5" 289 | idna = ">=2.5,<3" 290 | urllib3 = ">=1.21.1,<1.27" 291 | 292 | [package.extras] 293 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 294 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] 295 | 296 | [[package]] 297 | category = "dev" 298 | description = "Python Library for Tom's Obvious, Minimal Language" 299 | name = "toml" 300 | optional = false 301 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 302 | version = "0.10.2" 303 | 304 | [[package]] 305 | category = "dev" 306 | description = "Traitlets Python configuration system" 307 | name = "traitlets" 308 | optional = false 309 | python-versions = ">=3.7" 310 | version = "5.0.5" 311 | 312 | [package.dependencies] 313 | ipython-genutils = "*" 314 | 315 | [package.extras] 316 | test = ["pytest"] 317 | 318 | [[package]] 319 | category = "dev" 320 | description = "a fork of Python 2 and 3 ast modules with type comment support" 321 | name = "typed-ast" 322 | optional = false 323 | python-versions = "*" 324 | version = "1.4.1" 325 | 326 | [[package]] 327 | category = "dev" 328 | description = "Backported and Experimental Type Hints for Python 3.5+" 329 | name = "typing-extensions" 330 | optional = false 331 | python-versions = "*" 332 | version = "3.7.4.3" 333 | 334 | [[package]] 335 | category = "main" 336 | description = "HTTP library with thread-safe connection pooling, file post, and more." 337 | name = "urllib3" 338 | optional = false 339 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 340 | version = "1.26.2" 341 | 342 | [package.extras] 343 | brotli = ["brotlipy (>=0.6.0)"] 344 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 345 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] 346 | 347 | [[package]] 348 | category = "dev" 349 | description = "Measures the displayed width of unicode strings in a terminal" 350 | name = "wcwidth" 351 | optional = false 352 | python-versions = "*" 353 | version = "0.2.5" 354 | 355 | [metadata] 356 | content-hash = "24eaedfac33a20590d9756f94f8c511e58a981f31fe9478f6dec7ae0b5bde8cd" 357 | lock-version = "1.0" 358 | python-versions = "^3.8" 359 | 360 | [metadata.files] 361 | appdirs = [ 362 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 363 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 364 | ] 365 | appnope = [ 366 | {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, 367 | {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, 368 | ] 369 | backcall = [ 370 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 371 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 372 | ] 373 | black = [ 374 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 375 | ] 376 | certifi = [ 377 | {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, 378 | {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, 379 | ] 380 | chardet = [ 381 | {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, 382 | {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, 383 | ] 384 | click = [ 385 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 386 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 387 | ] 388 | colorama = [ 389 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 390 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 391 | ] 392 | coloredlogs = [ 393 | {file = "coloredlogs-15.0-py2.py3-none-any.whl", hash = "sha256:b7f630a8297a66984b6bae0f6a1b0e0afb9f2f6838ea3bfa58f50d3d13e133d6"}, 394 | {file = "coloredlogs-15.0.tar.gz", hash = "sha256:5e78691e2673a8e294499e1832bb13efcfb44a86b92e18109fa18951093218ab"}, 395 | ] 396 | decorator = [ 397 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 398 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 399 | ] 400 | docopt = [ 401 | {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, 402 | ] 403 | humanfriendly = [ 404 | {file = "humanfriendly-9.1-py2.py3-none-any.whl", hash = "sha256:d5c731705114b9ad673754f3317d9fa4c23212f36b29bdc4272a892eafc9bc72"}, 405 | {file = "humanfriendly-9.1.tar.gz", hash = "sha256:066562956639ab21ff2676d1fda0b5987e985c534fc76700a19bd54bcb81121d"}, 406 | ] 407 | idna = [ 408 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 409 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 410 | ] 411 | ipython = [ 412 | {file = "ipython-7.19.0-py3-none-any.whl", hash = "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f"}, 413 | {file = "ipython-7.19.0.tar.gz", hash = "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a"}, 414 | ] 415 | ipython-genutils = [ 416 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 417 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 418 | ] 419 | jedi = [ 420 | {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, 421 | {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, 422 | ] 423 | mypy-extensions = [ 424 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 425 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 426 | ] 427 | parso = [ 428 | {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, 429 | {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, 430 | ] 431 | pathspec = [ 432 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 433 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 434 | ] 435 | pexpect = [ 436 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 437 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 438 | ] 439 | pickleshare = [ 440 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 441 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 442 | ] 443 | prompt-toolkit = [ 444 | {file = "prompt_toolkit-3.0.8-py3-none-any.whl", hash = "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"}, 445 | {file = "prompt_toolkit-3.0.8.tar.gz", hash = "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c"}, 446 | ] 447 | ptyprocess = [ 448 | {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, 449 | {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, 450 | ] 451 | pygments = [ 452 | {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, 453 | {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, 454 | ] 455 | pyreadline = [ 456 | {file = "pyreadline-2.1.win-amd64.exe", hash = "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"}, 457 | {file = "pyreadline-2.1.win32.exe", hash = "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e"}, 458 | {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, 459 | ] 460 | regex = [ 461 | {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, 462 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, 463 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, 464 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, 465 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, 466 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, 467 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, 468 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, 469 | {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, 470 | {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, 471 | {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, 472 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, 473 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, 474 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, 475 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, 476 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, 477 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, 478 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, 479 | {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, 480 | {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, 481 | {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, 482 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, 483 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, 484 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, 485 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, 486 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, 487 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, 488 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, 489 | {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, 490 | {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, 491 | {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, 492 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, 493 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, 494 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, 495 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, 496 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, 497 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, 498 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, 499 | {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, 500 | {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, 501 | {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, 502 | ] 503 | requests = [ 504 | {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, 505 | {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, 506 | ] 507 | toml = [ 508 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 509 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 510 | ] 511 | traitlets = [ 512 | {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, 513 | {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, 514 | ] 515 | typed-ast = [ 516 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 517 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 518 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 519 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 520 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 521 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 522 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 523 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, 524 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 525 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 526 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 527 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 528 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 529 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, 530 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 531 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 532 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 533 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 534 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 535 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, 536 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 537 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 538 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 539 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, 540 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, 541 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, 542 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, 543 | {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, 544 | {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, 545 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 546 | ] 547 | typing-extensions = [ 548 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 549 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 550 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 551 | ] 552 | urllib3 = [ 553 | {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, 554 | {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, 555 | ] 556 | wcwidth = [ 557 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 558 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 559 | ] 560 | --------------------------------------------------------------------------------