├── 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 |
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 | 
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 | 
82 |
83 | __Big target list__
84 | 
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 |
13 | {% if targets.down %}
14 |
15 |
16 |
17 |
18 |
19 |
20 | | Name |
21 | HTTP Status/Expected |
22 | Error |
23 |
24 |
25 |
26 | {% for down_target in targets.down %}
27 |
28 | |
29 |
30 | {{ down_target.url }}
31 |
32 | |
33 |
34 | {{ down_target.response_http_code }}
35 | /
36 | {{ down_target.expected_http_code }}
37 | |
38 |
39 | {{ down_target.error }}
40 | |
41 |
42 |
43 | {% endfor %}
44 |
45 |
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 |
58 | {% endif %}
59 |
60 | {% for up_target in targets.up %}
61 | -
62 | {{ up_target.display }}
63 |
64 | ( {{ up_target.elapsed }}s )
65 |
66 |
67 | URL: {{ up_target.url}}
68 |
69 | Status: {{ up_target.response_http_code}}
70 |
71 |
72 | {% endfor %}
73 |
74 |
75 | {% endif %}
76 |
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 |
--------------------------------------------------------------------------------