├── docs ├── dashboard_example.png ├── dashboard_with_errors.png └── dashboard_tons_of_targets.png ├── app ├── settings.py ├── static │ ├── styles.css │ ├── 500_template.css │ └── targets_template.css ├── templates │ ├── 500_template.html │ └── targets_template.html ├── targets.yml ├── main.py └── targets.py ├── Dockerfile ├── Pipfile ├── LICENSE ├── .gitignore ├── README.md └── Pipfile.lock /docs/dashboard_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itzkvn/python-http-monitoring/HEAD/docs/dashboard_example.png -------------------------------------------------------------------------------- /docs/dashboard_with_errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itzkvn/python-http-monitoring/HEAD/docs/dashboard_with_errors.png -------------------------------------------------------------------------------- /docs/dashboard_tons_of_targets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itzkvn/python-http-monitoring/HEAD/docs/dashboard_tons_of_targets.png -------------------------------------------------------------------------------- /app/settings.py: -------------------------------------------------------------------------------- 1 | TARGETS_FILE = "targets.yml" 2 | REQUEST_RETRIES = 1 3 | REQUEST_RETRIES_WAIT = 1 4 | REQUEST_TIMEOUT = 5 5 | REQUEST_DEFAULT_HTTP_CODE = 200 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 2 | 3 | RUN pip3 install pipenv 4 | 5 | # -- Adding Pipfiles 6 | COPY Pipfile Pipfile 7 | COPY Pipfile.lock Pipfile.lock 8 | 9 | # -- Install dependencies: 10 | RUN set -ex && pipenv install --deploy --system 11 | 12 | COPY ./app /app -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | black = "*" 8 | 9 | [packages] 10 | pyyaml = "*" 11 | fastapi = "*" 12 | jinja2 = "*" 13 | aiofiles = "*" 14 | uvicorn = {extras = ["standard"],version = "*"} 15 | httpx = "*" 16 | 17 | [requires] 18 | python_version = "3.7" 19 | 20 | [pipenv] 21 | allow_prereleases = true 22 | -------------------------------------------------------------------------------- /app/static/styles.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans); 2 | /* BODY AND HEADER */ 3 | 4 | body { 5 | background: rgb(22, 17, 34); 6 | background-image: radial-gradient(rgb(167, 153, 204) 1px, transparent 0); 7 | background-size: 100px 100px; 8 | background-position: -10px -10px; 9 | padding-left: 20%; 10 | padding-right: 20%; 11 | font-family: 'Open Sans', serif; 12 | } -------------------------------------------------------------------------------- /app/templates/500_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | python-http-monitoring 7 | 8 | 9 | 10 | 11 | 12 |
13 |

500 Server Error

14 |
15 |

{{ error }}

16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /app/static/500_template.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans); 2 | * { 3 | position: relative; 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | .centered { 10 | height: 100vh; 11 | display: flex; 12 | flex-direction: column; 13 | justify-content: center; 14 | align-items: center; 15 | } 16 | 17 | .centered h1, .container p { 18 | color: rgb(250, 250, 250); 19 | } 20 | 21 | h1 { 22 | margin-bottom: 50px; 23 | font-family: 'Open Sans', sans-serif; 24 | font-weight: bold; 25 | font-size: 50px; 26 | } 27 | 28 | .message { 29 | display: inline-block; 30 | overflow: hidden; 31 | font-family: 'Open Sans', serif; 32 | font-size: 30px; 33 | } -------------------------------------------------------------------------------- /app/targets.yml: -------------------------------------------------------------------------------- 1 | targets: 2 | - display: Coronavirus - Worldometer 3 | url: https://www.worldometers.info/coronavirus/ 4 | expected_http_code: 200 5 | - display: Population - Worldometer 6 | url: https://www.worldometers.info/population/ 7 | expected_http_code: 200 8 | - display: Google 9 | url: https://www.google.com 10 | expected_http_code: 200 11 | - display: HTTP Easter Egg 12 | url: https://httpstatuses.com/418 13 | - display: Reddit Bad 14 | url: https://reddit.com/ 15 | expected_http_code: 200 16 | - display: Behance Found 17 | url: https://behance.net/ 18 | expected_http_code: 302 19 | - display: Behance Content 20 | url: https://www.behance.net/ 21 | expected_http_code: 200 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 itzk 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 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime 3 | 4 | from fastapi import FastAPI, Request 5 | from fastapi.responses import HTMLResponse 6 | from fastapi.staticfiles import StaticFiles 7 | from fastapi.templating import Jinja2Templates 8 | 9 | from settings import REQUEST_RETRIES, REQUEST_RETRIES_WAIT, REQUEST_TIMEOUT 10 | 11 | import targets 12 | 13 | REQUESTS_INFO = { 14 | "retries": REQUEST_RETRIES, 15 | "retries_wait": REQUEST_RETRIES_WAIT, 16 | "timeout": REQUEST_TIMEOUT, 17 | } 18 | 19 | app = FastAPI( 20 | title="python-http-monitoring", 21 | description='HTTP(s) "monitoring" webpage via FastAPI+Jinja2. Inspired by https://github.com/RaymiiOrg/bash-http-monitoring', 22 | version="0.1.0", 23 | docs_url=None, 24 | redoc_url=None, 25 | ) 26 | 27 | app.mount("/static", StaticFiles(directory="static"), name="static") 28 | templates = Jinja2Templates(directory="templates") 29 | 30 | 31 | @app.get("/target/status", response_class=HTMLResponse) 32 | async def get_targets_status(request: Request): 33 | start = time.monotonic() 34 | try: 35 | targets_status = await targets.get_targets_status() 36 | except Exception as ex: 37 | return templates.TemplateResponse( 38 | "500_template.html", 39 | { 40 | "request": request, 41 | "error": str(ex) or str(type(ex)), 42 | }, 43 | ) 44 | elapsed = round(time.monotonic() - start, 2) * 1000 45 | 46 | return templates.TemplateResponse( 47 | "targets_template.html", 48 | { 49 | "request": request, 50 | "targets": targets_status, 51 | "elapsed": f"{elapsed:.2f}", 52 | "requests_info": REQUESTS_INFO, 53 | "now": datetime.now().strftime("%a %b %d %H:%M:%S %Y"), 54 | }, 55 | ) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # VSCode 2 | .vscode/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 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 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /app/targets.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | import yaml 5 | from typing import NamedTuple, Optional 6 | 7 | from pydantic import BaseModel 8 | 9 | import httpx 10 | from httpx import ConnectTimeout, ConnectError 11 | 12 | from settings import ( 13 | TARGETS_FILE, 14 | REQUEST_RETRIES, 15 | REQUEST_RETRIES_WAIT, 16 | REQUEST_TIMEOUT, 17 | REQUEST_DEFAULT_HTTP_CODE, 18 | ) 19 | 20 | 21 | class TargetStatus(BaseModel): 22 | display: str 23 | url: str 24 | response_http_code: int 25 | expected_http_code: int 26 | elapsed: float 27 | error: Optional[str] 28 | 29 | 30 | def get_targets(): 31 | # TODO: Proper "schema" validation 32 | with open(TARGETS_FILE) as file: 33 | targets = yaml.safe_load(file.read()) 34 | if not all( 35 | [target.get("display") and target.get("url") for target in targets["targets"]] 36 | ): 37 | raise AssertionError( 38 | f"Couldn't find 'display' or 'url' for all targets in {TARGETS_FILE}" 39 | ) 40 | return targets 41 | 42 | 43 | async def get_target_status(target: dict): 44 | display = target["display"] 45 | url = target["url"] 46 | expected_http_code = target.get("expected_http_code") or REQUEST_DEFAULT_HTTP_CODE 47 | 48 | for RETRY_NUMBER in range(REQUEST_RETRIES): 49 | error = None 50 | start = time.monotonic() 51 | try: 52 | async with httpx.AsyncClient() as client: 53 | response = await client.get( 54 | url, allow_redirects=False, timeout=REQUEST_TIMEOUT 55 | ) 56 | response_http_code = response.status_code 57 | except ConnectError as ex: 58 | response_http_code = -1 59 | error = str(ex) or str(type(ex)) 60 | except ConnectTimeout as ex: 61 | response_http_code = -1 62 | error = f"Request timeout after {REQUEST_TIMEOUT}s" 63 | elapsed = round(time.monotonic() - start, 2) 64 | 65 | if not error: 66 | break 67 | 68 | await asyncio.sleep(REQUEST_RETRIES_WAIT) 69 | 70 | if not error and response_http_code != expected_http_code: 71 | error = "Status code does not match expected code" 72 | 73 | return TargetStatus( 74 | display=display, 75 | url=url, 76 | response_http_code=response_http_code, 77 | expected_http_code=expected_http_code, 78 | elapsed=elapsed, 79 | error=error, 80 | ) 81 | 82 | 83 | async def get_targets_status(): 84 | targets = get_targets() 85 | targets = targets["targets"] 86 | targets_status = await asyncio.gather( 87 | *(get_target_status(target=target) for target in targets) 88 | ) 89 | 90 | return { 91 | "up": [target for target in targets_status if not target.error], 92 | "down": [target for target in targets_status if target.error], 93 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-http-monitoring 2 | 3 | ![](./docs/dashboard_example.png) 4 | 5 | HTTP(s) "monitoring" powered by FastAPI+Jinja2+aiohttp. 6 | 7 | Inspired by [bash-http-monitoring](https://github.com/RaymiiOrg/bash-http-monitoring). 8 | 9 | Installation can be done with pipenv or Docker. 10 | Targets can be changed on runtime since they are read in each request (if you're running on Docker just mount the file as a volume). 11 | Request customization is also possible (retries per request, time between retries, etc.). 12 | 13 | Targets will be flagged as "down" if they take longer than the timeout or the expected http status code is different than the one expected. 14 | 15 | TODO (No ETA...): 16 | ``` 17 | Notifications 18 | Historic metrics 19 | Support other HTTP methods 20 | ``` 21 | 22 | ## Installation 23 | 24 | Minimum requirements to run locally: 25 | - \>Python 3.7 26 | - [pipenv](https://pypi.org/project/pipenv/) 27 | 28 | Otherwise, if you want to run it in a container: 29 | - Docker 30 | 31 | 32 | Locally: 33 | 34 | ```bash 35 | git clone https://github.com/itzkvn/python-http-monitoring.git 36 | cd python-http-monitoring 37 | pipenv shell 38 | pipenv install 39 | cd app 40 | uvicorn main:app --port 18080 41 | ``` 42 | 43 | Docker: 44 | 45 | If you want to try it out: 46 | 47 | ```bash 48 | docker run -it --rm -p 18080:80 kollowz/python-http-monitoring 49 | ``` 50 | 51 | If you want to mount a configuration file: 52 | 53 | ```bash 54 | docker run -it --rm -p 18080:80 -v "/path/to/targets.yml:/app/targets.yml" kollowz/python-http-monitoring 55 | ``` 56 | 57 | Either way you access the website at 127.0.0.1:18080/target/status. 58 | 59 | ## Configuration 60 | 61 | Configuration for this proyect consists in two files: 62 | 63 | ```app/targets.yml``` and ```app/settings.py``` 64 | 65 | targets.yml: targets to check 66 | Each target consists of: 67 | - display: Display name in the website (Maybe you want to see "My website" instead of "http://yourwebsite.com"). 68 | - url: URL to check (ex: "https://www.google.com"). 69 | - expected_http_code: Status code that will be validated against response status code (this is, mostly, what makes a check go either green or red). 70 | 71 | settings.py: "backend" configuration 72 | - TARGETS_FILE: Configuration file for targets. 73 | - REQUEST_RETRIES: # of retries per url (only retries if it fails). 74 | - REQUEST_RETRIES_WAIT: # of seconds between retries. 75 | - REQUEST_TIMEOUT: # of seconds till timeout per request. 76 | - REQUEST_HTTP_CODE: Default expected HTTP status code. 77 | 78 | ## Screenshots 79 | 80 | __Error view__ 81 | ![](./docs/dashboard_with_errors.png) 82 | 83 | __Big target list__ 84 | ![](./docs/dashboard_tons_of_targets.png) 85 | 86 | ## ¿Why did I make this? 87 | 88 | Saw [bash-http-monitoring](https://github.com/RaymiiOrg/bash-http-monitoring) on [r/selfhosted](https://www.reddit.com/r/selfhosted/comments/klao26/bash_http_monitoring_dashboard/) and had a weekend free to hack on something. Thought this was a fun enough challenge so here it is :) 89 | -------------------------------------------------------------------------------- /app/templates/targets_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | python-http-monitoring 7 | 8 | 9 | 10 | 11 | 12 |

Status Dashboard

13 | {% if targets.down %} 14 |
15 |

↓↓↓ {{ targets.down|length }} error(s) ↓↓↓

16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for down_target in targets.down %} 27 | 28 | 33 | 38 | 41 | 42 | 43 | {% endfor %} 44 | 45 |
NameHTTP Status/ExpectedError
29 | 30 | {{ down_target.url }} 31 | 32 | 34 | {{ down_target.response_http_code }} 35 | / 36 | {{ down_target.expected_http_code }} 37 | 39 | {{ down_target.error }} 40 |
46 |

47 | Tried to reach each target {{ requests_info.retries }} times. 48 | Waited {{ requests_info.retries_wait }}s in between requests. 49 | Timed out after: {{ requests_info.timeout }}s. 50 |

51 | 52 |
53 | {% endif %} 54 | {% if targets.up %} 55 |
56 | {% if not targets.down %} 57 |

↑↑↑ All services up ↑↑↑

58 | {% endif %} 59 | 74 |
75 | {% endif %} 76 |
77 |

Last check: {{ now }}

78 |

Duration: {{ elapsed }}ms

79 |

By itzkvn @ python-http-monitoring

81 |
82 | 83 | 84 | -------------------------------------------------------------------------------- /app/static/targets_template.css: -------------------------------------------------------------------------------- 1 | #status-header { 2 | color: floralwhite; 3 | } 4 | 5 | /* DOWN TARGETS START */ 6 | 7 | #down_targets-header { 8 | background-color: rgb(248, 215, 218); 9 | color: rgb(114, 28, 36); 10 | border: 1px solid rgb(245, 198, 203); 11 | border-radius: 5px; 12 | padding: 8px 50px 8px 8px; 13 | } 14 | 15 | #down_targets-separator { 16 | border: 1px solid dimgray; 17 | } 18 | 19 | #down_targets-table { 20 | border-collapse: collapse; 21 | width: 100%; 22 | background: rgb(250, 250, 250); 23 | } 24 | 25 | #down_targets-table th, #down_targets-table td { 26 | padding: 8px 50px 8px 8px; 27 | } 28 | 29 | #down_targets-table th { 30 | background-color: rgb(248, 215, 218); 31 | color: rgb(114, 28, 36); 32 | border: 1px solid rgb(245, 198, 203); 33 | } 34 | 35 | #down_targets-table tr, #down_targets-table tr a { 36 | background-color: rgb(255, 150, 170); 37 | color: rgb(22, 17, 34); 38 | } 39 | 40 | #down_targets-table tr:hover, #down_targets-table tr:hover a { 41 | background-color: #fafafa; 42 | } 43 | 44 | #down_targets-table th { 45 | padding-top: 12px; 46 | padding-bottom: 12px; 47 | color: darkslategray; 48 | text-align: left; 49 | } 50 | 51 | #retry-message { 52 | color: rgb(250, 250, 250); 53 | font-weight: bold; 54 | } 55 | 56 | /* DOWN TARGETS DOWN */ 57 | 58 | /* UP TARGETS */ 59 | 60 | #up_targets-header { 61 | background-color: rgb(212, 237, 218); 62 | color: rgb(21, 87, 36); 63 | border: 1px solid rgb(195, 230, 203); 64 | border-radius: 5px; 65 | padding: 8px 50px 8px 8px; 66 | } 67 | 68 | #up_targets-list { 69 | padding-left: 0px; 70 | } 71 | 72 | #up_targets-list>li { 73 | text-align: center; 74 | color: white; 75 | background-color: MediumSeaGreen; 76 | border: 2px solid MediumSeaGreen; 77 | border-radius: 5px; 78 | display: inline-block; 79 | margin-top: 10px; 80 | padding: 10px; 81 | } 82 | 83 | /* UP TARGETS DOWN */ 84 | 85 | /* INFO */ 86 | 87 | #info p, a { 88 | color: rgb(250, 250, 250); 89 | font-weight: bold; 90 | } 91 | 92 | .up_targets-list-item { 93 | position: relative; 94 | display: inline-block; 95 | border-bottom: 1px dotted black; 96 | } 97 | 98 | .up_targets-list-item .up_targets-list-item-tooltip { 99 | visibility: hidden; 100 | display: inline-block; 101 | white-space: nowrap; 102 | background-color: rgb(135, 158, 148); 103 | color: rgb(250, 250, 250); 104 | text-align: left; 105 | font-size: medium; 106 | font-weight: bold; 107 | border-radius: 6px; 108 | padding: 8px; 109 | opacity: 0; 110 | transition: opacity .2s .2s ease-out; 111 | /* transition: all 0.1s 1s ease-out; */ 112 | position: absolute; 113 | z-index: 1; 114 | top: 101%; 115 | left: 50%; 116 | margin-left: -60px; 117 | } 118 | 119 | .up_targets-list-item .up_targets-list-item-tooltip::after { 120 | content: ""; 121 | position: absolute; 122 | bottom: 100%; 123 | margin-left: 0px; 124 | border-width: 5px; 125 | border-style: solid; 126 | border-color: transparent transparent rgb(135, 158, 148) transparent 127 | } 128 | 129 | .up_targets-list-item:hover .up_targets-list-item-tooltip { 130 | visibility: visible; 131 | opacity: 1; 132 | } -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "d1347148193f0815e313fde1803b64795dbab2eaf665ac2d48c57c4faecb40c4" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aiofiles": { 20 | "hashes": [ 21 | "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27", 22 | "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092" 23 | ], 24 | "index": "pypi", 25 | "version": "==0.6.0" 26 | }, 27 | "certifi": { 28 | "hashes": [ 29 | "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", 30 | "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" 31 | ], 32 | "version": "==2020.12.5" 33 | }, 34 | "click": { 35 | "hashes": [ 36 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 37 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 38 | ], 39 | "version": "==7.1.2" 40 | }, 41 | "fastapi": { 42 | "hashes": [ 43 | "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb", 44 | "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e" 45 | ], 46 | "index": "pypi", 47 | "version": "==0.63.0" 48 | }, 49 | "h11": { 50 | "hashes": [ 51 | "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", 52 | "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" 53 | ], 54 | "version": "==0.12.0" 55 | }, 56 | "httpcore": { 57 | "hashes": [ 58 | "sha256:420700af11db658c782f7e8fda34f9dcd95e3ee93944dd97d78cb70247e0cd06", 59 | "sha256:dd1d762d4f7c2702149d06be2597c35fb154c5eff9789a8c5823fbcf4d2978d6" 60 | ], 61 | "version": "==0.12.2" 62 | }, 63 | "httptools": { 64 | "hashes": [ 65 | "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be", 66 | "sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d", 67 | "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce", 68 | "sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2", 69 | "sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6", 70 | "sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f", 71 | "sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009", 72 | "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce", 73 | "sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a", 74 | "sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c", 75 | "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4", 76 | "sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437" 77 | ], 78 | "version": "==0.1.1" 79 | }, 80 | "httpx": { 81 | "hashes": [ 82 | "sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537", 83 | "sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b" 84 | ], 85 | "index": "pypi", 86 | "version": "==0.16.1" 87 | }, 88 | "idna": { 89 | "hashes": [ 90 | "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16", 91 | "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1" 92 | ], 93 | "version": "==3.1" 94 | }, 95 | "jinja2": { 96 | "hashes": [ 97 | "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", 98 | "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" 99 | ], 100 | "index": "pypi", 101 | "version": "==3.0.0a1" 102 | }, 103 | "markupsafe": { 104 | "hashes": [ 105 | "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", 106 | "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", 107 | "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", 108 | "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", 109 | "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", 110 | "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", 111 | "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", 112 | "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", 113 | "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", 114 | "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", 115 | "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", 116 | "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", 117 | "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", 118 | "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", 119 | "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", 120 | "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", 121 | "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", 122 | "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", 123 | "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", 124 | "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", 125 | "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", 126 | "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" 127 | ], 128 | "version": "==2.0.0a1" 129 | }, 130 | "pydantic": { 131 | "hashes": [ 132 | "sha256:025bf13ce27990acc059d0c5be46f416fc9b293f45363b3d19855165fee1874f", 133 | "sha256:185e18134bec5ef43351149fe34fda4758e53d05bb8ea4d5928f0720997b79ef", 134 | "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9", 135 | "sha256:24ca47365be2a5a3cc3f4a26dcc755bcdc9f0036f55dcedbd55663662ba145ec", 136 | "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229", 137 | "sha256:475f2fa134cf272d6631072554f845d0630907fce053926ff634cc6bc45bf1af", 138 | "sha256:514b473d264671a5c672dfb28bdfe1bf1afd390f6b206aa2ec9fed7fc592c48e", 139 | "sha256:59e45f3b694b05a69032a0d603c32d453a23f0de80844fb14d55ab0c6c78ff2f", 140 | "sha256:5b24e8a572e4b4c18f614004dda8c9f2c07328cb5b6e314d6e1bbd536cb1a6c1", 141 | "sha256:6e3874aa7e8babd37b40c4504e3a94cc2023696ced5a0500949f3347664ff8e2", 142 | "sha256:8d72e814c7821125b16f1553124d12faba88e85405b0864328899aceaad7282b", 143 | "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009", 144 | "sha256:b2b054d095b6431cdda2f852a6d2f0fdec77686b305c57961b4c5dd6d863bf3c", 145 | "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd", 146 | "sha256:d1fe3f0df8ac0f3a9792666c69a7cd70530f329036426d06b4f899c025aca74e", 147 | "sha256:d8df4b9090b595511906fa48deda47af04e7d092318bfb291f4d45dfb6bb2127", 148 | "sha256:dba5c1f0a3aeea5083e75db9660935da90216f8a81b6d68e67f54e135ed5eb23", 149 | "sha256:e682f6442ebe4e50cb5e1cfde7dda6766fb586631c3e5569f6aa1951fd1a76ef", 150 | "sha256:ecb54491f98544c12c66ff3d15e701612fc388161fd455242447083350904730", 151 | "sha256:f5b06f5099e163295b8ff5b1b71132ecf5866cc6e7f586d78d7d3fd6e8084608", 152 | "sha256:f6864844b039805add62ebe8a8c676286340ba0c6d043ae5dea24114b82a319e", 153 | "sha256:ffd180ebd5dd2a9ac0da4e8b995c9c99e7c74c31f985ba090ee01d681b1c4b95" 154 | ], 155 | "version": "==1.7.3" 156 | }, 157 | "python-dotenv": { 158 | "hashes": [ 159 | "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e", 160 | "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0" 161 | ], 162 | "version": "==0.15.0" 163 | }, 164 | "pyyaml": { 165 | "hashes": [ 166 | "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", 167 | "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", 168 | "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", 169 | "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", 170 | "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", 171 | "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", 172 | "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", 173 | "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", 174 | "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", 175 | "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", 176 | "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", 177 | "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", 178 | "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" 179 | ], 180 | "index": "pypi", 181 | "version": "==5.3.1" 182 | }, 183 | "rfc3986": { 184 | "extras": [ 185 | "idna2008" 186 | ], 187 | "hashes": [ 188 | "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d", 189 | "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50" 190 | ], 191 | "version": "==1.4.0" 192 | }, 193 | "sniffio": { 194 | "hashes": [ 195 | "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", 196 | "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" 197 | ], 198 | "version": "==1.2.0" 199 | }, 200 | "starlette": { 201 | "hashes": [ 202 | "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9", 203 | "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc" 204 | ], 205 | "version": "==0.13.6" 206 | }, 207 | "typing-extensions": { 208 | "hashes": [ 209 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 210 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 211 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 212 | ], 213 | "markers": "python_version < '3.8'", 214 | "version": "==3.7.4.3" 215 | }, 216 | "uvicorn": { 217 | "extras": [ 218 | "standard" 219 | ], 220 | "hashes": [ 221 | "sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c", 222 | "sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355" 223 | ], 224 | "index": "pypi", 225 | "version": "==0.13.3" 226 | }, 227 | "uvloop": { 228 | "hashes": [ 229 | "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd", 230 | "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e", 231 | "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09", 232 | "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726", 233 | "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891", 234 | "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7", 235 | "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5", 236 | "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95", 237 | "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362" 238 | ], 239 | "version": "==0.14.0" 240 | }, 241 | "watchgod": { 242 | "hashes": [ 243 | "sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a", 244 | "sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858" 245 | ], 246 | "version": "==0.6" 247 | }, 248 | "websockets": { 249 | "hashes": [ 250 | "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", 251 | "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", 252 | "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", 253 | "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", 254 | "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", 255 | "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", 256 | "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", 257 | "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", 258 | "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", 259 | "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", 260 | "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", 261 | "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", 262 | "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", 263 | "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", 264 | "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", 265 | "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", 266 | "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", 267 | "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", 268 | "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", 269 | "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", 270 | "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", 271 | "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" 272 | ], 273 | "version": "==8.1" 274 | } 275 | }, 276 | "develop": { 277 | "appdirs": { 278 | "hashes": [ 279 | "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", 280 | "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" 281 | ], 282 | "version": "==1.4.4" 283 | }, 284 | "black": { 285 | "hashes": [ 286 | "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" 287 | ], 288 | "index": "pypi", 289 | "version": "==20.8b1" 290 | }, 291 | "click": { 292 | "hashes": [ 293 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 294 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 295 | ], 296 | "version": "==7.1.2" 297 | }, 298 | "mypy-extensions": { 299 | "hashes": [ 300 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 301 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 302 | ], 303 | "version": "==0.4.3" 304 | }, 305 | "pathspec": { 306 | "hashes": [ 307 | "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", 308 | "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" 309 | ], 310 | "version": "==0.8.1" 311 | }, 312 | "regex": { 313 | "hashes": [ 314 | "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538", 315 | "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4", 316 | "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc", 317 | "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa", 318 | "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444", 319 | "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1", 320 | "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af", 321 | "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8", 322 | "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9", 323 | "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88", 324 | "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba", 325 | "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364", 326 | "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e", 327 | "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7", 328 | "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0", 329 | "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31", 330 | "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683", 331 | "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee", 332 | "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b", 333 | "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884", 334 | "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c", 335 | "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e", 336 | "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562", 337 | "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85", 338 | "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c", 339 | "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6", 340 | "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d", 341 | "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b", 342 | "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70", 343 | "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b", 344 | "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b", 345 | "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f", 346 | "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0", 347 | "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5", 348 | "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5", 349 | "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f", 350 | "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e", 351 | "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512", 352 | "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d", 353 | "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917", 354 | "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f" 355 | ], 356 | "version": "==2020.11.13" 357 | }, 358 | "toml": { 359 | "hashes": [ 360 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 361 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 362 | ], 363 | "version": "==0.10.2" 364 | }, 365 | "typed-ast": { 366 | "hashes": [ 367 | "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", 368 | "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", 369 | "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", 370 | "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", 371 | "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", 372 | "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", 373 | "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", 374 | "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", 375 | "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", 376 | "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", 377 | "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", 378 | "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", 379 | "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", 380 | "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", 381 | "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", 382 | "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", 383 | "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", 384 | "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", 385 | "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", 386 | "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", 387 | "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", 388 | "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", 389 | "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", 390 | "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", 391 | "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", 392 | "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", 393 | "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", 394 | "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", 395 | "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", 396 | "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" 397 | ], 398 | "version": "==1.4.2" 399 | }, 400 | "typing-extensions": { 401 | "hashes": [ 402 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 403 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 404 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 405 | ], 406 | "markers": "python_version < '3.8'", 407 | "version": "==3.7.4.3" 408 | } 409 | } 410 | } 411 | --------------------------------------------------------------------------------