├── app ├── __init__.py ├── db │ ├── impl │ │ ├── __init__.py │ │ └── mongo_manager.py │ ├── __init__.py │ ├── database_manager.py │ └── models.py ├── rest │ ├── __init__.py │ └── posts.py ├── config.py └── main.py ├── example.env ├── run_api_tests.sh ├── tavern_tests ├── common.yaml └── test_posts.tavern.yaml ├── .gitignore ├── Dockerfile ├── pyproject.toml ├── docker-compose.yml ├── LICENSE ├── README.md └── poetry.lock /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/db/impl/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/rest/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | DB_PATH=mongodb://mongo_user:mongo_password@mongo:27017 -------------------------------------------------------------------------------- /run_api_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker-compose run web py.test tavern_tests/test_*.tavern.yaml -v 3 | -------------------------------------------------------------------------------- /tavern_tests/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Common test information 3 | description: Common settings 4 | 5 | variables: 6 | host: http://web:8000 7 | -------------------------------------------------------------------------------- /app/db/__init__.py: -------------------------------------------------------------------------------- 1 | from app.db.database_manager import DatabaseManager 2 | from app.db.impl.mongo_manager import MongoManager 3 | 4 | db = MongoManager() 5 | 6 | 7 | async def get_database() -> DatabaseManager: 8 | return db 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled python modules. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | /dist/ 6 | 7 | # Python egg metadata, regenerated from source files by setuptools. 8 | /*.egg-info 9 | /*.egg 10 | .vscode 11 | .coverage 12 | .pytest_cache 13 | .pypirc 14 | .vim 15 | .idea 16 | .env 17 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | from pydantic import BaseSettings 4 | 5 | 6 | class Config(BaseSettings): 7 | app_name: str = "MongoDB API" 8 | db_path: str 9 | 10 | class Config: 11 | env_file = ".env" 12 | 13 | 14 | @lru_cache() 15 | def get_config(): 16 | return Config() 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Pull base image 2 | FROM python:3.8 3 | 4 | # Set environment varibles 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONUNBUFFERED 1 7 | 8 | WORKDIR /code/ 9 | 10 | # Install dependencies 11 | COPY poetry.lock / 12 | COPY pyproject.toml . 13 | RUN pip install poetry && \ 14 | poetry config virtualenvs.create false && \ 15 | poetry install 16 | 17 | COPY . /code/ 18 | 19 | EXPOSE 8000 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fastapi-async-mongodb" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Michał Rosiak "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | motor = "^2.3.0" 10 | fastapi = "^0.61.1" 11 | uvicorn = "^0.12.1" 12 | python-dotenv = "^0.14.0" 13 | tavern = "^1.7.0" 14 | pytest = "^6.1.1" 15 | 16 | [tool.poetry.dev-dependencies] 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | web: 5 | build: . 6 | command: bash -c "uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload" 7 | volumes: 8 | - .:/code 9 | ports: 10 | - 8000:8000 11 | depends_on: 12 | - mongo 13 | mongo: 14 | image: mongo 15 | restart: always 16 | environment: 17 | MONGO_INITDB_ROOT_USERNAME: mongo_user 18 | MONGO_INITDB_ROOT_PASSWORD: mongo_password 19 | ports: 20 | - 27017:27017 # remove this line on prod 21 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | from fastapi import FastAPI 3 | 4 | from app.config import get_config 5 | from app.db import db 6 | from app.rest import posts 7 | 8 | app = FastAPI(title="Async FastAPI") 9 | 10 | app.include_router(posts.router, prefix='/api/posts') 11 | 12 | 13 | @app.on_event("startup") 14 | async def startup(): 15 | config = get_config() 16 | await db.connect_to_database(path=config.db_path) 17 | 18 | 19 | @app.on_event("shutdown") 20 | async def shutdown(): 21 | await db.close_database_connection() 22 | 23 | 24 | if __name__ == "__main__": 25 | uvicorn.run(app, host="0.0.0.0", port=8000) 26 | -------------------------------------------------------------------------------- /tavern_tests/test_posts.tavern.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | test_name: Tests posts 3 | 4 | includes: 5 | - !include common.yaml 6 | 7 | stages: 8 | - name: Getting empty posts list 9 | request: 10 | url: "{host}/api/posts/" 11 | method: GET 12 | response: 13 | status_code: 200 14 | json: [] 15 | 16 | - name: Creating post without parameters 17 | request: 18 | url: "{host}/api/posts/" 19 | method: POST 20 | response: 21 | status_code: 422 22 | 23 | - name: Creating post with parameters 24 | request: 25 | url: "{host}/api/posts/" 26 | method: POST 27 | json: 28 | title: 'Post title' 29 | description: 'Post description' 30 | response: 31 | status_code: 201 32 | 33 | - name: Getting posts list 34 | request: 35 | url: "{host}/api/posts/" 36 | method: GET 37 | response: 38 | status_code: 200 39 | -------------------------------------------------------------------------------- /app/db/database_manager.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import List 3 | 4 | from app.db.models import PostDB, OID 5 | 6 | 7 | class DatabaseManager(object): 8 | @property 9 | def client(self): 10 | raise NotImplementedError 11 | 12 | @property 13 | def db(self): 14 | raise NotImplementedError 15 | 16 | @abstractmethod 17 | async def connect_to_database(self, path: str): 18 | pass 19 | 20 | @abstractmethod 21 | async def close_database_connection(self): 22 | pass 23 | 24 | @abstractmethod 25 | async def get_posts(self) -> List[PostDB]: 26 | pass 27 | 28 | @abstractmethod 29 | async def get_post(self, post_id: OID) -> PostDB: 30 | pass 31 | 32 | @abstractmethod 33 | async def add_post(self, post: PostDB): 34 | pass 35 | 36 | @abstractmethod 37 | async def update_post(self, post_id: OID, post: PostDB): 38 | pass 39 | 40 | @abstractmethod 41 | async def delete_post(self, post_id: OID): 42 | pass 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michal Rosiak 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. -------------------------------------------------------------------------------- /app/rest/posts.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | from app.db import DatabaseManager, get_database 4 | from app.db.models import PostDB, OID 5 | 6 | router = APIRouter() 7 | 8 | 9 | @router.get('/') 10 | async def all_posts(db: DatabaseManager = Depends(get_database)): 11 | posts = await db.get_posts() 12 | return posts 13 | 14 | 15 | @router.get('/{post_id}') 16 | async def one_post(post_id: OID, db: DatabaseManager = Depends(get_database)): 17 | post = await db.get_post(post_id=post_id) 18 | return post 19 | 20 | 21 | @router.put('/{post_id}') 22 | async def update_post(post_id: OID, post: PostDB, db: DatabaseManager = Depends(get_database)): 23 | post = await db.update_post(post=post, post_id=post_id) 24 | return post 25 | 26 | 27 | @router.post('/', status_code=201) 28 | async def add_post(post_response: PostDB, db: DatabaseManager = Depends(get_database)): 29 | post = await db.add_post(post_response) 30 | return post 31 | 32 | 33 | @router.delete('/{post_id}') 34 | async def delete_post(post_id: OID, db: DatabaseManager = Depends(get_database)): 35 | await db.delete_post(post_id=post_id) 36 | -------------------------------------------------------------------------------- /app/db/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | I preferred using DB postfix for db models. 3 | It will not be confused with response objects - if you will need anything other than a simple CRUD. 4 | """ 5 | from typing import Optional 6 | 7 | from bson import ObjectId 8 | from pydantic.main import BaseModel 9 | 10 | 11 | class OID(str): 12 | @classmethod 13 | def __get_validators__(cls): 14 | yield cls.validate 15 | 16 | @classmethod 17 | def validate(cls, v): 18 | if v == '': 19 | raise TypeError('ObjectId is empty') 20 | if ObjectId.is_valid(v) is False: 21 | raise TypeError('ObjectId invalid') 22 | return str(v) 23 | 24 | 25 | class BaseDBModel(BaseModel): 26 | class Config: 27 | orm_mode = True 28 | allow_population_by_field_name = True 29 | 30 | @classmethod 31 | def alias_generator(cls, string: str) -> str: 32 | """ Camel case generator """ 33 | temp = string.split('_') 34 | return temp[0] + ''.join(ele.title() for ele in temp[1:]) 35 | 36 | 37 | class PostDB(BaseDBModel): 38 | id: Optional[OID] 39 | title: str 40 | description: str 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple example with FastAPI + MongoDB 2 | 3 | >*Plans: complete, minimalistic template based on external services. 4 | Focused on performance, less own code and infrastructure.* 5 | 6 | ## Features 7 | 8 | - Docker with [MongoDB](https://www.mongodb.com) and [FastAPI](http://fastapi.tiangolo.com) 9 | - [Poetry](https://python-poetry.org) as dependency manager 10 | - Works well **async** (all, with db) 11 | - Supported snake_case -> cammelCase conversion 12 | - Env file parsed by Pydantic 13 | - **ObjectID** works well with **FastAPI** & **Pydantic** (I've created custom field. Compatible with FastAPI generic docs) 14 | - Structure with **Dependency Injection** (database implementation) 15 | 16 | Build on **Python: 3.8**. 17 | 18 | 19 | ## Installation and usage 20 | 21 | - Create env from template: ```cp example.env .env``` (only once) 22 | - Run docker stack ```sudo docker-compose up``` 23 | 24 | ## TODO 25 | 26 | > Example is completely and works very well. In the future probably I add more. 27 | 28 | - Beanie as Mongo ORM 29 | - Scheme for MongoDB 30 | - More examples with custom response models 31 | - [Maybe] File handling with external provider (Amazon S3, DO Spaces) 32 | - [Maybe] Authorization by external provider (JuicyAuth.dev) 33 | -------------------------------------------------------------------------------- /app/db/impl/mongo_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List 3 | 4 | from bson import ObjectId 5 | from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase 6 | 7 | from app.db import DatabaseManager 8 | from app.db.models import PostDB, OID 9 | 10 | 11 | class MongoManager(DatabaseManager): 12 | client: AsyncIOMotorClient = None 13 | db: AsyncIOMotorDatabase = None 14 | 15 | async def connect_to_database(self, path: str): 16 | logging.info("Connecting to MongoDB.") 17 | self.client = AsyncIOMotorClient( 18 | path, 19 | maxPoolSize=10, 20 | minPoolSize=10) 21 | self.db = self.client.main_db 22 | logging.info("Connected to MongoDB.") 23 | 24 | async def close_database_connection(self): 25 | logging.info("Closing connection with MongoDB.") 26 | self.client.close() 27 | logging.info("Closed connection with MongoDB.") 28 | 29 | async def get_posts(self) -> List[PostDB]: 30 | posts_list = [] 31 | posts_q = self.db.posts.find() 32 | async for post in posts_q: 33 | posts_list.append(PostDB(**post, id=post['_id'])) 34 | return posts_list 35 | 36 | async def get_post(self, post_id: OID) -> PostDB: 37 | post_q = await self.db.posts.find_one({'_id': ObjectId(post_id)}) 38 | if post_q: 39 | return PostDB(**post_q, id=post_q['_id']) 40 | 41 | async def delete_post(self, post_id: OID): 42 | await self.db.posts.delete_one({'_id': ObjectId(post_id)}) 43 | 44 | async def update_post(self, post_id: OID, post: PostDB): 45 | await self.db.posts.update_one({'_id': ObjectId(post_id)}, 46 | {'$set': post.dict(exclude={'id'})}) 47 | 48 | async def add_post(self, post: PostDB): 49 | await self.db.posts.insert_one(post.dict(exclude={'id'})) 50 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.0" 4 | description = "Atomic file writes." 5 | category = "main" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "20.2.0" 12 | description = "Classes Without Boilerplate" 13 | category = "main" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | 17 | [package.extras] 18 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] 19 | docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] 20 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 21 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 22 | 23 | [[package]] 24 | name = "certifi" 25 | version = "2020.6.20" 26 | description = "Python package for providing Mozilla's CA Bundle." 27 | category = "main" 28 | optional = false 29 | python-versions = "*" 30 | 31 | [[package]] 32 | name = "chardet" 33 | version = "3.0.4" 34 | description = "Universal encoding detector for Python 2 and 3" 35 | category = "main" 36 | optional = false 37 | python-versions = "*" 38 | 39 | [[package]] 40 | name = "click" 41 | version = "7.1.2" 42 | description = "Composable command line interface toolkit" 43 | category = "main" 44 | optional = false 45 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 46 | 47 | [[package]] 48 | name = "colorama" 49 | version = "0.4.3" 50 | description = "Cross-platform colored terminal text." 51 | category = "main" 52 | optional = false 53 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 54 | 55 | [[package]] 56 | name = "docopt" 57 | version = "0.6.2" 58 | description = "Pythonic argument parser, that will make you smile" 59 | category = "main" 60 | optional = false 61 | python-versions = "*" 62 | 63 | [[package]] 64 | name = "fastapi" 65 | version = "0.61.1" 66 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 67 | category = "main" 68 | optional = false 69 | python-versions = ">=3.6" 70 | 71 | [package.dependencies] 72 | pydantic = ">=1.0.0,<2.0.0" 73 | starlette = "0.13.6" 74 | 75 | [package.extras] 76 | all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=3.0.0,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn (>=0.11.5,<0.12.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] 77 | dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn (>=0.11.5,<0.12.0)", "graphene (>=2.1.8,<3.0.0)"] 78 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.5.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer (>=0.3.0,<0.4.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"] 79 | test = ["pytest (5.4.3)", "pytest-cov (2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (0.782)", "flake8 (>=3.8.3,<4.0.0)", "black (19.10b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] 80 | 81 | [[package]] 82 | name = "h11" 83 | version = "0.10.0" 84 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 85 | category = "main" 86 | optional = false 87 | python-versions = "*" 88 | 89 | [[package]] 90 | name = "idna" 91 | version = "2.10" 92 | description = "Internationalized Domain Names in Applications (IDNA)" 93 | category = "main" 94 | optional = false 95 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 96 | 97 | [[package]] 98 | name = "iniconfig" 99 | version = "1.0.1" 100 | description = "iniconfig: brain-dead simple config-ini parsing" 101 | category = "main" 102 | optional = false 103 | python-versions = "*" 104 | 105 | [[package]] 106 | name = "jmespath" 107 | version = "0.10.0" 108 | description = "JSON Matching Expressions" 109 | category = "main" 110 | optional = false 111 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 112 | 113 | [[package]] 114 | name = "motor" 115 | version = "2.3.0" 116 | description = "Non-blocking MongoDB driver for Tornado or asyncio" 117 | category = "main" 118 | optional = false 119 | python-versions = ">=3.5.2" 120 | 121 | [package.dependencies] 122 | pymongo = ">=3.11,<4" 123 | 124 | [[package]] 125 | name = "packaging" 126 | version = "20.4" 127 | description = "Core utilities for Python packages" 128 | category = "main" 129 | optional = false 130 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 131 | 132 | [package.dependencies] 133 | pyparsing = ">=2.0.2" 134 | six = "*" 135 | 136 | [[package]] 137 | name = "paho-mqtt" 138 | version = "1.5.0" 139 | description = "MQTT version 3.1.1 client class" 140 | category = "main" 141 | optional = false 142 | python-versions = "*" 143 | 144 | [package.extras] 145 | proxy = ["pysocks"] 146 | 147 | [[package]] 148 | name = "pbr" 149 | version = "5.5.0" 150 | description = "Python Build Reasonableness" 151 | category = "main" 152 | optional = false 153 | python-versions = ">=2.6" 154 | 155 | [[package]] 156 | name = "pluggy" 157 | version = "0.13.1" 158 | description = "plugin and hook calling mechanisms for python" 159 | category = "main" 160 | optional = false 161 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 162 | 163 | [package.extras] 164 | dev = ["pre-commit", "tox"] 165 | 166 | [[package]] 167 | name = "py" 168 | version = "1.9.0" 169 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 170 | category = "main" 171 | optional = false 172 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 173 | 174 | [[package]] 175 | name = "pydantic" 176 | version = "1.6.1" 177 | description = "Data validation and settings management using python 3.6 type hinting" 178 | category = "main" 179 | optional = false 180 | python-versions = ">=3.6" 181 | 182 | [package.extras] 183 | dotenv = ["python-dotenv (>=0.10.4)"] 184 | email = ["email-validator (>=1.0.3)"] 185 | typing_extensions = ["typing-extensions (>=3.7.2)"] 186 | 187 | [[package]] 188 | name = "pyjwt" 189 | version = "1.7.1" 190 | description = "JSON Web Token implementation in Python" 191 | category = "main" 192 | optional = false 193 | python-versions = "*" 194 | 195 | [package.extras] 196 | crypto = ["cryptography (>=1.4)"] 197 | flake8 = ["flake8", "flake8-import-order", "pep8-naming"] 198 | test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] 199 | 200 | [[package]] 201 | name = "pykwalify" 202 | version = "1.7.0" 203 | description = "Python lib/cli for JSON/YAML schema validation" 204 | category = "main" 205 | optional = false 206 | python-versions = "*" 207 | 208 | [package.dependencies] 209 | docopt = ">=0.6.2" 210 | python-dateutil = ">=2.4.2" 211 | PyYAML = ">=3.11" 212 | 213 | [package.extras] 214 | ruamel = ["ruamel.yaml (>=0.11.0,<0.16.0)"] 215 | 216 | [[package]] 217 | name = "pymongo" 218 | version = "3.11.0" 219 | description = "Python driver for MongoDB " 220 | category = "main" 221 | optional = false 222 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 223 | 224 | [package.extras] 225 | aws = ["pymongo-auth-aws (<2.0.0)"] 226 | encryption = ["pymongocrypt (<2.0.0)"] 227 | gssapi = ["pykerberos"] 228 | ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] 229 | snappy = ["python-snappy"] 230 | srv = ["dnspython (>=1.16.0,<1.17.0)"] 231 | tls = ["ipaddress"] 232 | zstd = ["zstandard"] 233 | 234 | [[package]] 235 | name = "pyparsing" 236 | version = "2.4.7" 237 | description = "Python parsing module" 238 | category = "main" 239 | optional = false 240 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 241 | 242 | [[package]] 243 | name = "pytest" 244 | version = "6.1.1" 245 | description = "pytest: simple powerful testing with Python" 246 | category = "main" 247 | optional = false 248 | python-versions = ">=3.5" 249 | 250 | [package.dependencies] 251 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 252 | attrs = ">=17.4.0" 253 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 254 | iniconfig = "*" 255 | packaging = "*" 256 | pluggy = ">=0.12,<1.0" 257 | py = ">=1.8.2" 258 | toml = "*" 259 | 260 | [package.extras] 261 | checkqa_mypy = ["mypy (0.780)"] 262 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 263 | 264 | [[package]] 265 | name = "python-box" 266 | version = "5.1.1" 267 | description = "Advanced Python dictionaries with dot notation access" 268 | category = "main" 269 | optional = false 270 | python-versions = ">=3.6" 271 | 272 | [package.extras] 273 | pyyaml = ["pyyaml"] 274 | all = ["ruamel.yaml", "toml", "msgpack"] 275 | msgpack = ["msgpack"] 276 | "ruamel.yaml" = ["ruamel.yaml"] 277 | toml = ["toml"] 278 | 279 | [[package]] 280 | name = "python-dateutil" 281 | version = "2.8.1" 282 | description = "Extensions to the standard Python datetime module" 283 | category = "main" 284 | optional = false 285 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 286 | 287 | [package.dependencies] 288 | six = ">=1.5" 289 | 290 | [[package]] 291 | name = "python-dotenv" 292 | version = "0.14.0" 293 | description = "Add .env support to your django/flask apps in development and deployments" 294 | category = "main" 295 | optional = false 296 | python-versions = "*" 297 | 298 | [package.extras] 299 | cli = ["click (>=5.0)"] 300 | 301 | [[package]] 302 | name = "pyyaml" 303 | version = "5.3.1" 304 | description = "YAML parser and emitter for Python" 305 | category = "main" 306 | optional = false 307 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 308 | 309 | [[package]] 310 | name = "requests" 311 | version = "2.24.0" 312 | description = "Python HTTP for Humans." 313 | category = "main" 314 | optional = false 315 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 316 | 317 | [package.dependencies] 318 | certifi = ">=2017.4.17" 319 | chardet = ">=3.0.2,<4" 320 | idna = ">=2.5,<3" 321 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 322 | 323 | [package.extras] 324 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 325 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] 326 | 327 | [[package]] 328 | name = "six" 329 | version = "1.15.0" 330 | description = "Python 2 and 3 compatibility utilities" 331 | category = "main" 332 | optional = false 333 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 334 | 335 | [[package]] 336 | name = "starlette" 337 | version = "0.13.6" 338 | description = "The little ASGI library that shines." 339 | category = "main" 340 | optional = false 341 | python-versions = ">=3.6" 342 | 343 | [package.extras] 344 | full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] 345 | 346 | [[package]] 347 | name = "stevedore" 348 | version = "3.2.2" 349 | description = "Manage dynamic plugins for Python applications" 350 | category = "main" 351 | optional = false 352 | python-versions = ">=3.6" 353 | 354 | [package.dependencies] 355 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 356 | 357 | [[package]] 358 | name = "tavern" 359 | version = "1.7.0" 360 | description = "Simple testing of RESTful APIs" 361 | category = "main" 362 | optional = false 363 | python-versions = "*" 364 | 365 | [package.dependencies] 366 | jmespath = "<1" 367 | paho-mqtt = ">=1.3.1,<=1.5.0" 368 | pyjwt = ">=1.7.1,<2" 369 | pykwalify = ">=1.7.0,<2" 370 | pytest = ">=5.4.3,<7" 371 | python-box = ">4,<6" 372 | PyYAML = ">=5.3.1,<6" 373 | requests = ">=2.22.0,<3" 374 | stevedore = "*" 375 | 376 | [package.extras] 377 | tests = ["pytest-cov", "colorlog", "faker", "prospector[with_mypy,with_pyroma] (>=1.3.0,<2)", "pygments", "pylint (2.5.2)", "black", "mypy", "mypy-extensions", "isort (<5)"] 378 | 379 | [[package]] 380 | name = "toml" 381 | version = "0.10.1" 382 | description = "Python Library for Tom's Obvious, Minimal Language" 383 | category = "main" 384 | optional = false 385 | python-versions = "*" 386 | 387 | [[package]] 388 | name = "urllib3" 389 | version = "1.25.10" 390 | description = "HTTP library with thread-safe connection pooling, file post, and more." 391 | category = "main" 392 | optional = false 393 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 394 | 395 | [package.extras] 396 | brotli = ["brotlipy (>=0.6.0)"] 397 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] 398 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] 399 | 400 | [[package]] 401 | name = "uvicorn" 402 | version = "0.12.1" 403 | description = "The lightning-fast ASGI server." 404 | category = "main" 405 | optional = false 406 | python-versions = "*" 407 | 408 | [package.dependencies] 409 | click = ">=7.0.0,<8.0.0" 410 | h11 = ">=0.8" 411 | 412 | [package.extras] 413 | standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6,<0.7)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0)", "colorama (>=0.4)"] 414 | 415 | [metadata] 416 | lock-version = "1.1" 417 | python-versions = "^3.8" 418 | content-hash = "e8c76f2e8b99989425b9148948205e278ba3b61a12e473598e81ec958c070a3f" 419 | 420 | [metadata.files] 421 | atomicwrites = [ 422 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 423 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 424 | ] 425 | attrs = [ 426 | {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, 427 | {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, 428 | ] 429 | certifi = [ 430 | {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, 431 | {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, 432 | ] 433 | chardet = [ 434 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 435 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 436 | ] 437 | click = [ 438 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 439 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 440 | ] 441 | colorama = [ 442 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 443 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 444 | ] 445 | docopt = [ 446 | {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, 447 | ] 448 | fastapi = [ 449 | {file = "fastapi-0.61.1-py3-none-any.whl", hash = "sha256:6cc31bb555dd8ca956d1d227477d661e4ac012337242a41d36214ffbda78bfe9"}, 450 | {file = "fastapi-0.61.1.tar.gz", hash = "sha256:61ed73b4304413a2ea618d1b95ea866ee386e0e62dd8659c4f5059286f4a39c2"}, 451 | ] 452 | h11 = [ 453 | {file = "h11-0.10.0-py2.py3-none-any.whl", hash = "sha256:9eecfbafc980976dbff26a01dd3487644dd5d00f8038584451fc64a660f7c502"}, 454 | {file = "h11-0.10.0.tar.gz", hash = "sha256:311dc5478c2568cc07262e0381cdfc5b9c6ba19775905736c87e81ae6662b9fd"}, 455 | ] 456 | idna = [ 457 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 458 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 459 | ] 460 | iniconfig = [ 461 | {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"}, 462 | {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"}, 463 | ] 464 | jmespath = [ 465 | {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, 466 | {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, 467 | ] 468 | motor = [ 469 | {file = "motor-2.3.0-py3-none-any.whl", hash = "sha256:428d94750123d19fcd0a89b8671ff9b4656f205217bad9f44161748c64c5fc80"}, 470 | {file = "motor-2.3.0.tar.gz", hash = "sha256:f1692b760d834707e3477996ce8d407af8cd61c1a2abedbf81c22ef14675e61a"}, 471 | ] 472 | packaging = [ 473 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, 474 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, 475 | ] 476 | paho-mqtt = [ 477 | {file = "paho-mqtt-1.5.0.tar.gz", hash = "sha256:e3d286198baaea195c8b3bc221941d25a3ab0e1507fc1779bdb7473806394be4"}, 478 | ] 479 | pbr = [ 480 | {file = "pbr-5.5.0-py2.py3-none-any.whl", hash = "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"}, 481 | {file = "pbr-5.5.0.tar.gz", hash = "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea"}, 482 | ] 483 | pluggy = [ 484 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 485 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 486 | ] 487 | py = [ 488 | {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, 489 | {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, 490 | ] 491 | pydantic = [ 492 | {file = "pydantic-1.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:418b84654b60e44c0cdd5384294b0e4bc1ebf42d6e873819424f3b78b8690614"}, 493 | {file = "pydantic-1.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4900b8820b687c9a3ed753684337979574df20e6ebe4227381d04b3c3c628f99"}, 494 | {file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b49c86aecde15cde33835d5d6360e55f5e0067bb7143a8303bf03b872935c75b"}, 495 | {file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2de562a456c4ecdc80cf1a8c3e70c666625f7d02d89a6174ecf63754c734592e"}, 496 | {file = "pydantic-1.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f769141ab0abfadf3305d4fcf36660e5cf568a666dd3efab7c3d4782f70946b1"}, 497 | {file = "pydantic-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dc946b07cf24bee4737ced0ae77e2ea6bc97489ba5a035b603bd1b40ad81f7e"}, 498 | {file = "pydantic-1.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:36dbf6f1be212ab37b5fda07667461a9219c956181aa5570a00edfb0acdfe4a1"}, 499 | {file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1783c1d927f9e1366e0e0609ae324039b2479a1a282a98ed6a6836c9ed02002c"}, 500 | {file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cf3933c98cb5e808b62fae509f74f209730b180b1e3c3954ee3f7949e083a7df"}, 501 | {file = "pydantic-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f8af9b840a9074e08c0e6dc93101de84ba95df89b267bf7151d74c553d66833b"}, 502 | {file = "pydantic-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:40d765fa2d31d5be8e29c1794657ad46f5ee583a565c83cea56630d3ae5878b9"}, 503 | {file = "pydantic-1.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3fa799f3cfff3e5f536cbd389368fc96a44bb30308f258c94ee76b73bd60531d"}, 504 | {file = "pydantic-1.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6c3f162ba175678218629f446a947e3356415b6b09122dcb364e58c442c645a7"}, 505 | {file = "pydantic-1.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:eb75dc1809875d5738df14b6566ccf9fd9c0bcde4f36b72870f318f16b9f5c20"}, 506 | {file = "pydantic-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:530d7222a2786a97bc59ee0e0ebbe23728f82974b1f1ad9a11cd966143410633"}, 507 | {file = "pydantic-1.6.1-py36.py37.py38-none-any.whl", hash = "sha256:b5b3489cb303d0f41ad4a7390cf606a5f2c7a94dcba20c051cd1c653694cb14d"}, 508 | {file = "pydantic-1.6.1.tar.gz", hash = "sha256:54122a8ed6b75fe1dd80797f8251ad2063ea348a03b77218d73ea9fe19bd4e73"}, 509 | ] 510 | pyjwt = [ 511 | {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, 512 | {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"}, 513 | ] 514 | pykwalify = [ 515 | {file = "pykwalify-1.7.0-py2.py3-none-any.whl", hash = "sha256:428733907fe5c458fbea5de63a755f938edccd622c7a1d0b597806141976f00e"}, 516 | {file = "pykwalify-1.7.0.tar.gz", hash = "sha256:7e8b39c5a3a10bc176682b3bd9a7422c39ca247482df198b402e8015defcceb2"}, 517 | ] 518 | pymongo = [ 519 | {file = "pymongo-3.11.0-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:7a4a6f5b818988a3917ec4baa91d1143242bdfece8d38305020463955961266a"}, 520 | {file = "pymongo-3.11.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c4869141e20769b65d2d72686e7a7eb141ce9f3168106bed3e7dcced54eb2422"}, 521 | {file = "pymongo-3.11.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:ef76535776c0708a85258f6dc51d36a2df12633c735f6d197ed7dfcaa7449b99"}, 522 | {file = "pymongo-3.11.0-cp27-cp27m-win32.whl", hash = "sha256:d226e0d4b9192d95079a9a29c04dd81816b1ce8903b8c174a39224fe978547cb"}, 523 | {file = "pymongo-3.11.0-cp27-cp27m-win_amd64.whl", hash = "sha256:68220b81850de8e966d4667d5c325a96c6ac0d6adb3d18935d6e3d325d441f48"}, 524 | {file = "pymongo-3.11.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f6efca006a81e1197b925a7d7b16b8f61980697bb6746587aad8842865233218"}, 525 | {file = "pymongo-3.11.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7307024b18266b302f4265da84bb1effb5d18999ef35b30d17592959568d5c0a"}, 526 | {file = "pymongo-3.11.0-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:8ea13d0348b4c96b437d944d7068d59ed4a6c98aaa6c40d8537a2981313f1c66"}, 527 | {file = "pymongo-3.11.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:6a15e2bee5c4188369a87ed6f02de804651152634a46cca91966a11c8abd2550"}, 528 | {file = "pymongo-3.11.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d64c98277ea80e4484f1332ab107e8dfd173a7dcf1bdbf10a9cccc97aaab145f"}, 529 | {file = "pymongo-3.11.0-cp34-cp34m-win32.whl", hash = "sha256:83c5a3ecd96a9f3f11cfe6dfcbcec7323265340eb24cc996acaecea129865a3a"}, 530 | {file = "pymongo-3.11.0-cp34-cp34m-win_amd64.whl", hash = "sha256:890b0f1e18dbd898aeb0ab9eae1ab159c6bcbe87f0abb065b0044581d8614062"}, 531 | {file = "pymongo-3.11.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:9fc17fdac8f1973850d42e51e8ba6149d93b1993ed6768a24f352f926dd3d587"}, 532 | {file = "pymongo-3.11.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:421aa1b92c291c429668bd8d8d8ec2bd00f183483a756928e3afbf2b6f941f00"}, 533 | {file = "pymongo-3.11.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a2787319dc69854acdfd6452e6a8ba8f929aeb20843c7f090e04159fc18e6245"}, 534 | {file = "pymongo-3.11.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:455f4deb00158d5ec8b1d3092df6abb681b225774ab8a59b3510293b4c8530e3"}, 535 | {file = "pymongo-3.11.0-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:25e617daf47d8dfd4e152c880cd0741cbdb48e51f54b8de9ddbfe74ecd87dd16"}, 536 | {file = "pymongo-3.11.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:7122ffe597b531fb065d3314e704a6fe152b81820ca5f38543e70ffcc95ecfd4"}, 537 | {file = "pymongo-3.11.0-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:d0565481dc196986c484a7fb13214fc6402201f7fb55c65fd215b3324962fe6c"}, 538 | {file = "pymongo-3.11.0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:4437300eb3a5e9cc1a73b07d22c77302f872f339caca97e9bf8cf45eca8fa0d2"}, 539 | {file = "pymongo-3.11.0-cp35-cp35m-win32.whl", hash = "sha256:d38b35f6eef4237b1d0d8e845fc1546dad85c55eba447e28c211da8c7ef9697c"}, 540 | {file = "pymongo-3.11.0-cp35-cp35m-win_amd64.whl", hash = "sha256:137e6fa718c7eff270dbd2fc4b90d94b1a69c9e9eb3f3de9e850a7fd33c822dc"}, 541 | {file = "pymongo-3.11.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0d660a186e36c526366edf8a64391874fe53cf8b7039224137aee0163c046df"}, 542 | {file = "pymongo-3.11.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d1b3366329c45a474b3bbc9b9c95d4c686e03f35da7fd12bc144626d1f2a7c04"}, 543 | {file = "pymongo-3.11.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b7c522292407fa04d8195032493aac937e253ad9ae524aab43b9d9d242571f03"}, 544 | {file = "pymongo-3.11.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9755c726aa6788f076114dfdc03b92b03ff8860316cca00902cce88bcdb5fedd"}, 545 | {file = "pymongo-3.11.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:50531caa7b4be1c4ed5e2d5793a4e51cc9bd62a919a6fd3299ef7c902e206eab"}, 546 | {file = "pymongo-3.11.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:cc4057f692ac35bbe82a0a908d42ce3a281c9e913290fac37d7fa3bd01307dfb"}, 547 | {file = "pymongo-3.11.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:213c445fe7e654621c6309e874627c35354b46ef3ee807f5a1927dc4b30e1a67"}, 548 | {file = "pymongo-3.11.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:4ae23fbbe9eadf61279a26eba866bbf161a6f7e2ffad14a42cf20e9cb8e94166"}, 549 | {file = "pymongo-3.11.0-cp36-cp36m-win32.whl", hash = "sha256:8deda1f7b4c03242f2a8037706d9584e703f3d8c74d6d9cac5833db36fe16c42"}, 550 | {file = "pymongo-3.11.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e8c446882cbb3774cd78c738c9f58220606b702b7c1655f1423357dc51674054"}, 551 | {file = "pymongo-3.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9de8427a5601799784eb0e7fa1b031aa64086ce04de29df775a8ca37eedac41"}, 552 | {file = "pymongo-3.11.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3d9bb1ba935a90ec4809a8031efd988bdb13cdba05d9e9a3e9bf151bf759ecde"}, 553 | {file = "pymongo-3.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:96782ebb3c9e91e174c333208b272ea144ed2a684413afb1038e3b3342230d72"}, 554 | {file = "pymongo-3.11.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:50127b13b38e8e586d5e97d342689405edbd74ad0bd891d97ee126a8c7b6e45f"}, 555 | {file = "pymongo-3.11.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:bd312794f51e37dcf77f013d40650fe4fbb211dd55ef2863839c37480bd44369"}, 556 | {file = "pymongo-3.11.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4797c0080f41eba90404335e5ded3aa66731d303293a675ff097ce4ea3025bb9"}, 557 | {file = "pymongo-3.11.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:1f865b1d1c191d785106f54df9abdc7d2f45a946b45fd1ea0a641b4f982a2a77"}, 558 | {file = "pymongo-3.11.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cccf1e7806f12300e3a3b48f219e111000c2538483e85c869c35c1ae591e6ce9"}, 559 | {file = "pymongo-3.11.0-cp37-cp37m-win32.whl", hash = "sha256:05fcc6f9c60e6efe5219fbb5a30258adb3d3e5cbd317068f3d73c09727f2abb6"}, 560 | {file = "pymongo-3.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9dbab90c348c512e03f146e93a5e2610acec76df391043ecd46b6b775d5397e6"}, 561 | {file = "pymongo-3.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:689142dc0c150e9cb7c012d84cac2c346d40beb891323afb6caf18ec4caafae0"}, 562 | {file = "pymongo-3.11.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4b32744901ee9990aa8cd488ec85634f443526def1e5190a407dc107148249d7"}, 563 | {file = "pymongo-3.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e6a15cf8f887d9f578dd49c6fb3a99d53e1d922fdd67a245a67488d77bf56eb2"}, 564 | {file = "pymongo-3.11.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e8d188ee39bd0ffe76603da887706e4e7b471f613625899ddf1e27867dc6a0d3"}, 565 | {file = "pymongo-3.11.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:9ee0eef254e340cc11c379f797af3977992a7f2c176f1a658740c94bf677e13c"}, 566 | {file = "pymongo-3.11.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:91e96bf85b7c07c827d339a386e8a3cf2e90ef098c42595227f729922d0851df"}, 567 | {file = "pymongo-3.11.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:ce208f80f398522e49d9db789065c8ad2cd37b21bd6b23d30053474b7416af11"}, 568 | {file = "pymongo-3.11.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:475a34a0745c456ceffaec4ce86b7e0983478f1b6140890dff7b161e7bcd895b"}, 569 | {file = "pymongo-3.11.0-cp38-cp38-win32.whl", hash = "sha256:40696a9a53faa7d85aaa6fd7bef1cae08f7882640bad08c350fb59dee7ad069b"}, 570 | {file = "pymongo-3.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:03dc64a9aa7a5d405aea5c56db95835f6a2fa31b3502c5af1760e0e99210be30"}, 571 | {file = "pymongo-3.11.0-py2.7-macosx-10.15-x86_64.egg", hash = "sha256:63a5387e496a98170ffe638b435c0832c0f2011a6f4ff7a2880f17669fff8c03"}, 572 | {file = "pymongo-3.11.0.tar.gz", hash = "sha256:076a7f2f7c251635cf6116ac8e45eefac77758ee5a77ab7bd2f63999e957613b"}, 573 | ] 574 | pyparsing = [ 575 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 576 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 577 | ] 578 | pytest = [ 579 | {file = "pytest-6.1.1-py3-none-any.whl", hash = "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9"}, 580 | {file = "pytest-6.1.1.tar.gz", hash = "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92"}, 581 | ] 582 | python-box = [ 583 | {file = "python-box-5.1.1.tar.gz", hash = "sha256:e51d03275f0d6fef3c155f79c19e542c2fb6d92e89eb385cd71ea22c5d4681b3"}, 584 | {file = "python_box-5.1.1-py3-none-any.whl", hash = "sha256:b7a6f3edd2f71e2475d93163b6465f637a2714b155acafef17408b06e55282b3"}, 585 | ] 586 | python-dateutil = [ 587 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 588 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 589 | ] 590 | python-dotenv = [ 591 | {file = "python-dotenv-0.14.0.tar.gz", hash = "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d"}, 592 | {file = "python_dotenv-0.14.0-py2.py3-none-any.whl", hash = "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"}, 593 | ] 594 | pyyaml = [ 595 | {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, 596 | {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, 597 | {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, 598 | {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, 599 | {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, 600 | {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, 601 | {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, 602 | {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, 603 | {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, 604 | {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, 605 | {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, 606 | ] 607 | requests = [ 608 | {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, 609 | {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, 610 | ] 611 | six = [ 612 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 613 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 614 | ] 615 | starlette = [ 616 | {file = "starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"}, 617 | {file = "starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc"}, 618 | ] 619 | stevedore = [ 620 | {file = "stevedore-3.2.2-py3-none-any.whl", hash = "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62"}, 621 | {file = "stevedore-3.2.2.tar.gz", hash = "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0"}, 622 | ] 623 | tavern = [ 624 | {file = "tavern-1.7.0-py3-none-any.whl", hash = "sha256:e6348321b0cde829bd796318d557cad52464184d3980477475fbcefa1030fff9"}, 625 | ] 626 | toml = [ 627 | {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, 628 | {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, 629 | ] 630 | urllib3 = [ 631 | {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, 632 | {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, 633 | ] 634 | uvicorn = [ 635 | {file = "uvicorn-0.12.1-py3-none-any.whl", hash = "sha256:d06a25caa8dc680ad92eb3ec67363f5281c092059613a1cc0100acba37fc0f45"}, 636 | {file = "uvicorn-0.12.1.tar.gz", hash = "sha256:a461e76406088f448f36323f5ac774d50e5a552b6ccb54e4fca8d83ef614a7c2"}, 637 | ] 638 | --------------------------------------------------------------------------------