├── project ├── app │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── ping.py │ │ ├── crud.py │ │ └── summaries.py │ ├── models │ │ ├── __init__.py │ │ ├── pydantic.py │ │ └── tortoise.py │ ├── config.py │ ├── summarizer.py │ ├── main.py │ └── db.py ├── tests │ ├── __init__.py │ ├── test_ping.py │ ├── docker-compose.yml │ ├── conftest.py │ ├── test_summaries.py │ └── test_summaries_unit.py ├── .dockerignore ├── setup.cfg ├── .coveragerc ├── db │ ├── create.sql │ └── Dockerfile ├── entrypoint.sh ├── Makefile ├── Pipfile ├── Dockerfile ├── Dockerfile.prod └── Pipfile.lock ├── .gitignore ├── release.sh ├── Pipfile.lock ├── docker-compose.yml └── .github └── workflows └── main.yml /project/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/app/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/app/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/.dockerignore: -------------------------------------------------------------------------------- 1 | env 2 | .dockerignore 3 | 4 | -------------------------------------------------------------------------------- /project/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 119 3 | -------------------------------------------------------------------------------- /project/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = tests/* 3 | branch = True 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | env 3 | .python-version 4 | .idea 5 | .coverage 6 | -------------------------------------------------------------------------------- /project/db/create.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE web_dev; 2 | CREATE DATABASE web_test; 3 | -------------------------------------------------------------------------------- /project/db/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM postgres:13-alpine 3 | 4 | # run create.sql on init 5 | ADD create.sql /docker-entrypoint-initdb.d 6 | -------------------------------------------------------------------------------- /project/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Waiting for postgres..." 4 | 5 | while ! nc -z web-db 5432; do 6 | sleep 0.1 7 | done 8 | 9 | echo "PostgreSQL started" 10 | 11 | exec "$@" 12 | -------------------------------------------------------------------------------- /project/tests/test_ping.py: -------------------------------------------------------------------------------- 1 | def test_ping(test_app): 2 | response = test_app.get("/ping") 3 | assert response.status_code == 200 4 | assert response.json() == {"environment": "dev", "ping": "pong!", "testing": True} 5 | -------------------------------------------------------------------------------- /project/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | pipenv run pytest -k "not unit" --cov=. tests 3 | pipenv run pytest -k "unit" -n auto 4 | 5 | style: 6 | pipenv run black . 7 | pipenv run isort . 8 | 9 | lint: 10 | pipenv run flake8 . 11 | pipenv run isort . --check-only 12 | pipenv run black . --check -------------------------------------------------------------------------------- /project/app/models/pydantic.py: -------------------------------------------------------------------------------- 1 | from pydantic import AnyHttpUrl, BaseModel 2 | 3 | 4 | class SummaryPayloadSchema(BaseModel): 5 | url: AnyHttpUrl 6 | 7 | 8 | class SummaryResponseSchema(SummaryPayloadSchema): 9 | id: int 10 | 11 | 12 | class SummaryUpdatePayloadSchema(SummaryPayloadSchema): 13 | summary: str 14 | -------------------------------------------------------------------------------- /project/app/api/ping.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | from app.config import Settings, get_settings 4 | 5 | router = APIRouter() 6 | 7 | 8 | @router.get("/ping") 9 | async def pong(settings: Settings = Depends(get_settings)): 10 | return { 11 | "ping": "pong!", 12 | "environment": settings.environment, 13 | "testing": settings.testing, 14 | } 15 | -------------------------------------------------------------------------------- /project/app/models/tortoise.py: -------------------------------------------------------------------------------- 1 | from tortoise import fields, models 2 | from tortoise.contrib.pydantic import pydantic_model_creator 3 | 4 | 5 | class TextSummary(models.Model): 6 | url = fields.TextField() 7 | summary = fields.TextField() 8 | created_at = fields.DatetimeField(auto_now_add=True) 9 | 10 | def __str__(self): 11 | return self.url 12 | 13 | 14 | SummarySchema = pydantic_model_creator(TextSummary) 15 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | IMAGE_ID=$(docker inspect ${HEROKU_REGISTRY_IMAGE} --format={{.Id}}) 6 | PAYLOAD='{"updates": [{"type": "web", "docker_image": "'"$IMAGE_ID"'"}]}' 7 | 8 | curl -n -X PATCH https://api.heroku.com/apps/$HEROKU_APP_NAME/formation \ 9 | -d "${PAYLOAD}" \ 10 | -H "Content-Type: application/json" \ 11 | -H "Accept: application/vnd.heroku+json; version=3.docker-releases" \ 12 | -H "Authorization: Bearer ${HEROKU_AUTH_TOKEN}" 13 | -------------------------------------------------------------------------------- /project/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | fastapi = "*" 8 | uvicorn = "*" 9 | asyncpg = "*" 10 | tortoise-orm = "*" 11 | gunicorn = "*" 12 | newspaper3k = "*" 13 | 14 | [dev-packages] 15 | pytest = "*" 16 | requests = "*" 17 | pytest-docker = "*" 18 | docker-compose = "*" 19 | pytest-cov = "*" 20 | flake8 = "*" 21 | black = "==20.8b1" 22 | isort = "*" 23 | pytest-xdist = "*" 24 | 25 | [requires] 26 | python_version = "3.8" 27 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "a36a5392bb1e8bbc06bfaa0761e52593cf2d83b486696bf54667ba8da616c839" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.9" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": {}, 19 | "develop": {} 20 | } 21 | -------------------------------------------------------------------------------- /project/app/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from functools import lru_cache 4 | 5 | from pydantic import AnyUrl, BaseSettings 6 | 7 | log = logging.getLogger("uvicorn") 8 | 9 | 10 | class Settings(BaseSettings): 11 | environment: str = os.getenv("ENVIRONMENT", "dev") 12 | testing: bool = os.getenv("TESTING", 0) 13 | database_url: AnyUrl = os.environ.get("DATABASE_URL") 14 | 15 | 16 | @lru_cache 17 | def get_settings() -> BaseSettings: 18 | log.info("Loading config settings from the environment...") 19 | return Settings() 20 | -------------------------------------------------------------------------------- /project/app/summarizer.py: -------------------------------------------------------------------------------- 1 | import nltk 2 | from newspaper import Article 3 | 4 | from app.models.tortoise import TextSummary 5 | 6 | 7 | async def generate_summary(summary_id: int, url: str) -> None: 8 | article = Article(url) 9 | article.download() 10 | article.parse() 11 | 12 | try: 13 | nltk.data.find("tokenizers/punkt") 14 | except LookupError: 15 | nltk.download("punkt") 16 | finally: 17 | article.nlp() 18 | 19 | summary = article.summary 20 | 21 | await TextSummary.filter(id=summary_id).update(summary=summary) 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: ./project 6 | command: uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000 7 | volumes: 8 | - ./project:/usr/src/app 9 | ports: 10 | - 8004:8000 11 | environment: 12 | - ENVIRONMENT=dev 13 | - TESTING=0 14 | - DATABASE_URL=postgres://postgres:postgres@web-db:5432/web_dev 15 | depends_on: 16 | - web-db 17 | 18 | web-db: 19 | build: 20 | context: ./project/db 21 | dockerfile: Dockerfile 22 | expose: 23 | - 5432 24 | environment: 25 | - POSTGRES_USER=postgres 26 | - POSTGRES_PASSWORD=postgres -------------------------------------------------------------------------------- /project/tests/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: 6 | context: ../ 7 | command: uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000 8 | ports: 9 | - 8000 10 | environment: 11 | - ENVIRONMENT=dev 12 | - TESTING=0 13 | - DATABASE_URL=postgres://postgres:postgres@web-db:5432/web_test 14 | depends_on: 15 | - web-db 16 | 17 | web-db: 18 | build: 19 | context: ../db 20 | dockerfile: Dockerfile 21 | expose: 22 | - 5432 23 | ports: 24 | - 5432 25 | environment: 26 | - POSTGRES_USER=postgres 27 | - POSTGRES_PASSWORD=postgres 28 | tmpfs: 29 | - /var/lib/postgresql -------------------------------------------------------------------------------- /project/app/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from fastapi import FastAPI 4 | 5 | from app.api import ping, summaries 6 | from app.db import init_db 7 | 8 | log = logging.getLogger("uvicorn") 9 | 10 | 11 | def create_application() -> FastAPI: 12 | application = FastAPI() 13 | application.include_router(ping.router) 14 | application.include_router( 15 | summaries.router, prefix="/summaries", tags=["summaries"] 16 | ) # new 17 | 18 | return application 19 | 20 | 21 | app = create_application() 22 | 23 | 24 | @app.on_event("startup") 25 | async def startup_event(): 26 | log.info("Starting up...") 27 | init_db(app) 28 | 29 | 30 | @app.on_event("shutdown") 31 | async def shutdown_event(): 32 | log.info("Shutting down...") 33 | -------------------------------------------------------------------------------- /project/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.8-slim-buster 2 | 3 | WORKDIR /usr/src/app 4 | 5 | ENV PYTHONDONTWRITEBYTECODE 1 # Prevents Python from writing pyc files to disc 6 | ENV PYTHONUNBUFFERED 1 # Prevents Python from buffering stdout and stderr 7 | 8 | 9 | RUN apt-get update && \ 10 | apt-get -y install netcat gcc && \ 11 | apt-get clean 12 | 13 | # install python dependencies 14 | COPY Pipfile Pipfile.lock ./ 15 | RUN apt-get update && \ 16 | apt-get -qy full-upgrade && \ 17 | apt-get install -qy curl && \ 18 | curl -sSL https://get.docker.com/ | sh 19 | 20 | RUN pip install --upgrade pip && \ 21 | pip install pipenv && \ 22 | pipenv install --dev --ignore-pipfile --deploy --system 23 | 24 | # add app 25 | COPY . . 26 | 27 | # add entrypoint.sh 28 | COPY ./entrypoint.sh . 29 | RUN chmod +x /usr/src/app/entrypoint.sh 30 | 31 | # run entrypoint.sh 32 | ENTRYPOINT ["/usr/src/app/entrypoint.sh"] 33 | -------------------------------------------------------------------------------- /project/app/db.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from fastapi import FastAPI 5 | from tortoise import Tortoise, run_async 6 | from tortoise.contrib.fastapi import register_tortoise 7 | 8 | log = logging.getLogger("uvicorn") 9 | 10 | 11 | def init_db(app: FastAPI) -> None: 12 | register_tortoise( 13 | app, 14 | db_url=os.environ.get("DATABASE_URL"), 15 | modules={"models": ["app.models.tortoise"]}, 16 | generate_schemas=False, 17 | add_exception_handlers=True, 18 | ) 19 | 20 | 21 | async def generate_schema() -> None: 22 | log.info("Initializing Tortoise...") 23 | 24 | await Tortoise.init( 25 | db_url=os.environ.get("DATABASE_URL"), 26 | modules={"models": ["models.tortoise"]}, 27 | ) 28 | log.info("Generating database schema via Tortoise...") 29 | await Tortoise.generate_schemas() 30 | await Tortoise.close_connections() 31 | 32 | 33 | if __name__ == "__main__": 34 | run_async(generate_schema()) 35 | -------------------------------------------------------------------------------- /project/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM python:3.8.8-slim-buster 3 | 4 | # create directory for the app user 5 | RUN mkdir -p /home/app 6 | 7 | # create the app user 8 | RUN addgroup --system app && adduser --system --group app 9 | 10 | # create the appropriate directories 11 | ENV HOME=/home/app 12 | ENV APP_HOME=/home/app/web 13 | RUN mkdir $APP_HOME 14 | WORKDIR $APP_HOME 15 | 16 | # set environment variables 17 | ENV PYTHONDONTWRITEBYTECODE 1 18 | ENV PYTHONUNBUFFERED 1 19 | ENV ENVIRONMENT prod 20 | ENV TESTING 0 21 | 22 | # install system dependencies 23 | COPY Pipfile Pipfile.lock ./ 24 | RUN pip install --upgrade pip && \ 25 | pip install pipenv && \ 26 | pipenv install --ignore-pipfile --deploy --system && \ 27 | pip install "uvicorn[standard]==0.13.4" 28 | 29 | # add app 30 | COPY . . 31 | 32 | # chown all the files to the app user 33 | RUN chown -R app:app $APP_HOME 34 | 35 | # change to the app user 36 | USER app 37 | 38 | # run gunicorn 39 | CMD gunicorn --bind 0.0.0.0:$PORT app.main:app -k uvicorn.workers.UvicornWorker -------------------------------------------------------------------------------- /project/app/api/crud.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from app.models.pydantic import SummaryPayloadSchema 4 | from app.models.tortoise import TextSummary 5 | 6 | 7 | async def post(payload: SummaryPayloadSchema) -> int: 8 | summary = TextSummary(url=payload.url, summary="") 9 | await summary.save() 10 | return summary.id 11 | 12 | 13 | async def get(id: int) -> Union[dict, None]: 14 | summary = await TextSummary.filter(id=id).first().values() 15 | if summary: 16 | return summary[0] 17 | return None 18 | 19 | 20 | async def get_all() -> List: 21 | summaries = await TextSummary.all().values() 22 | return summaries 23 | 24 | 25 | async def delete(id: int) -> int: 26 | summary = await TextSummary.filter(id=id).first().delete() 27 | return summary 28 | 29 | 30 | async def put(id: int, payload: SummaryPayloadSchema) -> Union[dict, None]: 31 | summary = await TextSummary.filter(id=id).update( 32 | url=payload.url, summary=payload.summary 33 | ) 34 | if summary: 35 | updated_summary = await TextSummary.filter(id=id).first().values() 36 | return updated_summary[0] 37 | return None 38 | -------------------------------------------------------------------------------- /project/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import pytest 5 | from starlette.testclient import TestClient 6 | from tortoise.contrib.fastapi import register_tortoise 7 | 8 | from app.api import summaries 9 | from app.config import Settings, get_settings 10 | from app.main import create_application 11 | 12 | 13 | def get_settings_override(): 14 | return Settings(testing=1, database_url=os.environ.get("DATABASE_TEST_URL")) 15 | 16 | 17 | @pytest.fixture(scope="session") 18 | def docker_compose_file(pytestconfig): 19 | return os.path.join(str(pytestconfig.rootdir), "tests", "docker-compose.yml") 20 | 21 | 22 | @pytest.fixture(scope="module") 23 | def test_app(): 24 | # set up 25 | app = create_application() 26 | app.dependency_overrides[get_settings] = get_settings_override 27 | with TestClient(app) as test_client: 28 | 29 | # testing 30 | yield test_client 31 | 32 | # tear down 33 | 34 | 35 | @pytest.fixture(scope="session") 36 | def db_url(docker_ip, docker_services): 37 | port = docker_services.port_for("web-db", 5432) 38 | db_name = "web_test" 39 | url = f"postgres://postgres:postgres@{docker_ip}:{port}/{db_name}" 40 | time.sleep(2) 41 | return url 42 | 43 | 44 | @pytest.fixture 45 | def patch_summary(monkeypatch): 46 | def mock_generate_summary(summary_id, url): 47 | return "summary" 48 | 49 | yield monkeypatch.setattr(summaries, "generate_summary", mock_generate_summary) 50 | 51 | 52 | @pytest.fixture(scope="module") 53 | def test_app_with_db(db_url): 54 | # set up 55 | app = create_application() 56 | app.dependency_overrides[get_settings] = get_settings_override 57 | register_tortoise( 58 | app, 59 | db_url=db_url, 60 | modules={"models": ["app.models.tortoise"]}, 61 | generate_schemas=True, 62 | add_exception_handlers=True, 63 | ) 64 | with TestClient(app) as test_client: 65 | # testing 66 | yield test_client 67 | 68 | # tear down 69 | -------------------------------------------------------------------------------- /project/app/api/summaries.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from fastapi import APIRouter, BackgroundTasks, HTTPException, Path 4 | 5 | from app.api import crud 6 | from app.models.tortoise import SummarySchema 7 | from app.summarizer import generate_summary 8 | 9 | from app.models.pydantic import ( # isort:skip 10 | SummaryPayloadSchema, 11 | SummaryResponseSchema, 12 | SummaryUpdatePayloadSchema, 13 | ) 14 | 15 | router = APIRouter() 16 | 17 | 18 | @router.get("/{id}/", response_model=SummarySchema) 19 | async def read_summary(id: int = Path(..., gt=0)) -> SummarySchema: 20 | summary = await crud.get(id) 21 | if not summary: 22 | raise HTTPException(status_code=404, detail="Summary not found") 23 | 24 | return summary 25 | 26 | 27 | @router.post("/", response_model=SummaryResponseSchema, status_code=201) 28 | async def create_summary( 29 | payload: SummaryPayloadSchema, background_tasks: BackgroundTasks 30 | ) -> SummaryResponseSchema: 31 | summary_id = await crud.post(payload) 32 | 33 | background_tasks.add_task(generate_summary, summary_id, payload.url) 34 | 35 | response_object = {"id": summary_id, "url": payload.url} 36 | return response_object 37 | 38 | 39 | @router.get("/", response_model=List[SummarySchema]) 40 | async def read_all_summaries() -> List[SummarySchema]: 41 | return await crud.get_all() 42 | 43 | 44 | @router.delete("/{id}/", response_model=SummaryResponseSchema) 45 | async def delete_summary(id: int = Path(..., gt=0)) -> SummaryResponseSchema: 46 | summary = await crud.get(id) 47 | if not summary: 48 | raise HTTPException(status_code=404, detail="Summary not found") 49 | 50 | await crud.delete(id) 51 | 52 | return summary 53 | 54 | 55 | @router.put("/{id}/", response_model=SummarySchema) 56 | async def update_summary( 57 | payload: SummaryUpdatePayloadSchema, id: int = Path(..., gt=0) 58 | ) -> SummarySchema: 59 | summary = await crud.put(id, payload) 60 | if not summary: 61 | raise HTTPException(status_code=404, detail="Summary not found") 62 | 63 | return summary 64 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration and Delivery 2 | 3 | on: [push] 4 | 5 | env: 6 | IMAGE: docker.pkg.github.com/$(echo $GITHUB_REPOSITORY | tr '[A-Z]' '[a-z]')/summarizer 7 | 8 | jobs: 9 | 10 | build: 11 | name: Build Docker Image 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout master 15 | uses: actions/checkout@v2.3.4 16 | - name: Log in to GitHub Packages 17 | run: echo ${GITHUB_TOKEN} | docker login -u ${GITHUB_ACTOR} --password-stdin docker.pkg.github.com 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | - name: Pull image 21 | run: | 22 | docker pull ${{ env.IMAGE }}:latest || true 23 | - name: Build image 24 | run: | 25 | docker build \ 26 | --cache-from ${{ env.IMAGE }}:latest \ 27 | --tag ${{ env.IMAGE }}:latest \ 28 | --file ./project/Dockerfile.prod \ 29 | "./project" 30 | - name: Push image 31 | run: | 32 | docker push ${{ env.IMAGE }}:latest 33 | 34 | test: 35 | name: Test Docker Image 36 | runs-on: ubuntu-latest 37 | needs: build 38 | steps: 39 | - name: Checkout master 40 | uses: actions/checkout@v2.3.4 41 | - name: Log in to GitHub Packages 42 | run: echo ${GITHUB_TOKEN} | docker login -u ${GITHUB_ACTOR} --password-stdin docker.pkg.github.com 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | - name: Pull image 46 | run: | 47 | docker pull ${{ env.IMAGE }}:latest || true 48 | - name: Build image 49 | run: | 50 | docker build \ 51 | --cache-from ${{ env.IMAGE }}:latest \ 52 | --tag ${{ env.IMAGE }}:test \ 53 | --file ./project/Dockerfile \ 54 | "./project" 55 | - name: Run container 56 | run: | 57 | docker run \ 58 | -d \ 59 | -v /var/run/docker.sock:/var/run/docker.sock \ 60 | --name fastapi-tdd \ 61 | -e PORT=8765 \ 62 | -e ENVIRONMENT=dev \ 63 | -e DATABASE_URL=sqlite://sqlite.db \ 64 | -p 5003:8765 \ 65 | ${{ env.IMAGE }}:test 66 | # - name: Tests 67 | # run: docker exec fastapi-tdd python -m pytest . 68 | - name: Flake8 69 | run: docker exec fastapi-tdd python -m flake8 . 70 | - name: Black 71 | run: docker exec fastapi-tdd python -m black . --check 72 | - name: isort 73 | run: docker exec fastapi-tdd python -m isort . --check-only 74 | 75 | deploy: 76 | name: Deploy to Heroku 77 | runs-on: ubuntu-latest 78 | needs: [build, test] 79 | env: 80 | HEROKU_APP_NAME: blooming-headland-23659 81 | HEROKU_REGISTRY_IMAGE: registry.heroku.com/${HEROKU_APP_NAME}/summarizer 82 | steps: 83 | - name: Checkout master 84 | uses: actions/checkout@v2.3.4 85 | - name: Log in to GitHub Packages 86 | run: echo ${GITHUB_TOKEN} | docker login -u ${GITHUB_ACTOR} --password-stdin docker.pkg.github.com 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | - name: Pull image 90 | run: | 91 | docker pull ${{ env.IMAGE }}:latest || true 92 | - name: Build image 93 | run: | 94 | docker build \ 95 | --cache-from ${{ env.IMAGE }}:latest \ 96 | --tag ${{ env.HEROKU_REGISTRY_IMAGE }}:latest \ 97 | --file ./project/Dockerfile.prod \ 98 | "./project" 99 | - name: Log in to the Heroku Container Registry 100 | run: docker login -u _ -p ${HEROKU_AUTH_TOKEN} registry.heroku.com 101 | env: 102 | HEROKU_AUTH_TOKEN: ${{ secrets.HEROKU_AUTH_TOKEN }} 103 | - name: Push to the registry 104 | run: docker push ${{ env.HEROKU_REGISTRY_IMAGE }} 105 | - name: Set environment variables 106 | run: | 107 | echo "HEROKU_REGISTRY_IMAGE=${{ env.HEROKU_REGISTRY_IMAGE }}" >> $GITHUB_ENV 108 | echo "HEROKU_AUTH_TOKEN=${{ secrets.HEROKU_AUTH_TOKEN }}" >> $GITHUB_ENV 109 | - name: Release 110 | run: | 111 | chmod +x ./release.sh 112 | ./release.sh -------------------------------------------------------------------------------- /project/tests/test_summaries.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | 5 | 6 | def test_create_summary(test_app_with_db, patch_summary): 7 | 8 | response = test_app_with_db.post( 9 | "/summaries/", data=json.dumps({"url": "https://foo.bar"}) 10 | ) 11 | 12 | assert response.status_code == 201 13 | assert response.json()["url"] == "https://foo.bar" 14 | 15 | 16 | def test_create_summaries_invalid_json(test_app): 17 | response = test_app.post("/summaries/", data=json.dumps({})) 18 | assert response.status_code == 422 19 | assert response.json() == { 20 | "detail": [ 21 | { 22 | "loc": ["body", "url"], 23 | "msg": "field required", 24 | "type": "value_error.missing", 25 | } 26 | ] 27 | } 28 | 29 | response = test_app.post("/summaries/", data=json.dumps({"url": "invalid://url"})) 30 | assert response.status_code == 422 31 | assert response.json()["detail"][0]["msg"] == "URL scheme not permitted" 32 | 33 | 34 | def test_read_summary(test_app_with_db, patch_summary): 35 | response = test_app_with_db.post( 36 | "/summaries/", data=json.dumps({"url": "https://foo.bar"}) 37 | ) 38 | summary_id = response.json()["id"] 39 | 40 | response = test_app_with_db.get(f"/summaries/{summary_id}/") 41 | assert response.status_code == 200 42 | 43 | response_dict = response.json() 44 | assert response_dict["id"] == summary_id 45 | assert response_dict["url"] == "https://foo.bar" 46 | # assert response_dict["summary"] 47 | assert response_dict["created_at"] 48 | 49 | 50 | def test_update_summary_incorrect_id(test_app_with_db): 51 | response = test_app_with_db.put( 52 | "/summaries/999/", 53 | data=json.dumps({"url": "https://foo.bar", "summary": "updated!"}), 54 | ) 55 | assert response.status_code == 404 56 | assert response.json()["detail"] == "Summary not found" 57 | 58 | response = test_app_with_db.put( 59 | "/summaries/0/", 60 | data=json.dumps({"url": "https://foo.bar", "summary": "updated!"}), 61 | ) 62 | assert response.status_code == 422 63 | assert response.json() == { 64 | "detail": [ 65 | { 66 | "loc": ["path", "id"], 67 | "msg": "ensure this value is greater than 0", 68 | "type": "value_error.number.not_gt", 69 | "ctx": {"limit_value": 0}, 70 | } 71 | ] 72 | } 73 | 74 | 75 | def test_read_all_summaries(test_app_with_db, patch_summary): 76 | response = test_app_with_db.post( 77 | "/summaries/", data=json.dumps({"url": "https://foo.bar"}) 78 | ) 79 | summary_id = response.json()["id"] 80 | 81 | response = test_app_with_db.get("/summaries/") 82 | assert response.status_code == 200 83 | 84 | response_list = response.json() 85 | assert len(list(filter(lambda d: d["id"] == summary_id, response_list))) == 1 86 | 87 | 88 | def test_remove_summary(test_app_with_db, patch_summary): 89 | response = test_app_with_db.post( 90 | "/summaries/", data=json.dumps({"url": "https://foo.bar"}) 91 | ) 92 | summary_id = response.json()["id"] 93 | 94 | response = test_app_with_db.delete(f"/summaries/{summary_id}/") 95 | assert response.status_code == 200 96 | assert response.json() == {"id": summary_id, "url": "https://foo.bar"} 97 | 98 | 99 | def test_remove_summary_incorrect_id(test_app_with_db): 100 | response = test_app_with_db.delete("/summaries/999/") 101 | assert response.status_code == 404 102 | assert response.json()["detail"] == "Summary not found" 103 | 104 | 105 | def test_update_summary(test_app_with_db, patch_summary): 106 | response = test_app_with_db.post( 107 | "/summaries/", data=json.dumps({"url": "https://foo.bar"}) 108 | ) 109 | summary_id = response.json()["id"] 110 | 111 | response = test_app_with_db.put( 112 | f"/summaries/{summary_id}/", 113 | data=json.dumps({"url": "https://foo.bar", "summary": "updated!"}), 114 | ) 115 | assert response.status_code == 200 116 | 117 | response_dict = response.json() 118 | assert response_dict["id"] == summary_id 119 | assert response_dict["url"] == "https://foo.bar" 120 | assert response_dict["summary"] == "updated!" 121 | assert response_dict["created_at"] 122 | 123 | 124 | @pytest.mark.parametrize( 125 | "summary_id, payload, status_code, detail", 126 | [ 127 | [ 128 | 999, 129 | {"url": "https://foo.bar", "summary": "updated!"}, 130 | 404, 131 | "Summary not found", 132 | ], 133 | [ 134 | 0, 135 | {"url": "https://foo.bar", "summary": "updated!"}, 136 | 422, 137 | [ 138 | { 139 | "loc": ["path", "id"], 140 | "msg": "ensure this value is greater than 0", 141 | "type": "value_error.number.not_gt", 142 | "ctx": {"limit_value": 0}, 143 | } 144 | ], 145 | ], 146 | [ 147 | 1, 148 | {}, 149 | 422, 150 | [ 151 | { 152 | "loc": ["body", "url"], 153 | "msg": "field required", 154 | "type": "value_error.missing", 155 | }, 156 | { 157 | "loc": ["body", "summary"], 158 | "msg": "field required", 159 | "type": "value_error.missing", 160 | }, 161 | ], 162 | ], 163 | [ 164 | 1, 165 | {"url": "https://foo.bar"}, 166 | 422, 167 | [ 168 | { 169 | "loc": ["body", "summary"], 170 | "msg": "field required", 171 | "type": "value_error.missing", 172 | } 173 | ], 174 | ], 175 | ], 176 | ) 177 | def test_update_summary_invalid( 178 | test_app_with_db, summary_id, payload, status_code, detail 179 | ): 180 | response = test_app_with_db.put( 181 | f"/summaries/{summary_id}/", data=json.dumps(payload) 182 | ) 183 | assert response.status_code == status_code 184 | assert response.json()["detail"] == detail 185 | 186 | 187 | def test_update_summary_invalid_url(test_app): 188 | response = test_app.put( 189 | "/summaries/1/", 190 | data=json.dumps({"url": "invalid://url", "summary": "updated!"}), 191 | ) 192 | assert response.status_code == 422 193 | assert response.json()["detail"][0]["msg"] == "URL scheme not permitted" 194 | -------------------------------------------------------------------------------- /project/tests/test_summaries_unit.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | import pytest 5 | 6 | from app.api import crud 7 | 8 | 9 | def test_create_summary(test_app, monkeypatch, patch_summary): 10 | test_request_payload = {"url": "https://foo.bar"} 11 | test_response_payload = {"id": 1, "url": "https://foo.bar"} 12 | 13 | async def mock_post(payload): 14 | return 1 15 | 16 | monkeypatch.setattr(crud, "post", mock_post) 17 | 18 | response = test_app.post( 19 | "/summaries/", 20 | data=json.dumps(test_request_payload), 21 | ) 22 | 23 | assert response.status_code == 201 24 | assert response.json() == test_response_payload 25 | 26 | 27 | def test_create_summaries_invalid_json(test_app): 28 | response = test_app.post("/summaries/", data=json.dumps({})) 29 | assert response.status_code == 422 30 | assert response.json() == { 31 | "detail": [ 32 | { 33 | "loc": ["body", "url"], 34 | "msg": "field required", 35 | "type": "value_error.missing", 36 | } 37 | ] 38 | } 39 | 40 | response = test_app.post("/summaries/", data=json.dumps({"url": "invalid://url"})) 41 | assert response.status_code == 422 42 | assert response.json()["detail"][0]["msg"] == "URL scheme not permitted" 43 | 44 | 45 | def test_read_summary(test_app, monkeypatch): 46 | test_data = { 47 | "id": 1, 48 | "url": "https://foo.bar", 49 | "summary": "summary", 50 | "created_at": datetime.utcnow().isoformat(), 51 | } 52 | 53 | async def mock_get(id): 54 | return test_data 55 | 56 | monkeypatch.setattr(crud, "get", mock_get) 57 | 58 | response = test_app.get("/summaries/1/") 59 | assert response.status_code == 200 60 | assert response.json() == test_data 61 | 62 | 63 | def test_read_summary_incorrect_id(test_app, monkeypatch): 64 | async def mock_get(id): 65 | return None 66 | 67 | monkeypatch.setattr(crud, "get", mock_get) 68 | 69 | response = test_app.get("/summaries/999/") 70 | assert response.status_code == 404 71 | assert response.json()["detail"] == "Summary not found" 72 | 73 | 74 | def test_read_all_summaries(test_app, monkeypatch): 75 | test_data = [ 76 | { 77 | "id": 1, 78 | "url": "https://foo.bar", 79 | "summary": "summary", 80 | "created_at": datetime.utcnow().isoformat(), 81 | }, 82 | { 83 | "id": 2, 84 | "url": "https://testdrivenn.io", 85 | "summary": "summary", 86 | "created_at": datetime.utcnow().isoformat(), 87 | }, 88 | ] 89 | 90 | async def mock_get_all(): 91 | return test_data 92 | 93 | monkeypatch.setattr(crud, "get_all", mock_get_all) 94 | 95 | response = test_app.get("/summaries/") 96 | assert response.status_code == 200 97 | assert response.json() == test_data 98 | 99 | 100 | def test_remove_summary(test_app, monkeypatch): 101 | async def mock_get(id): 102 | return { 103 | "id": 1, 104 | "url": "https://foo.bar", 105 | "summary": "summary", 106 | "created_at": datetime.utcnow().isoformat(), 107 | } 108 | 109 | monkeypatch.setattr(crud, "get", mock_get) 110 | 111 | async def mock_delete(id): 112 | return id 113 | 114 | monkeypatch.setattr(crud, "delete", mock_delete) 115 | 116 | response = test_app.delete("/summaries/1/") 117 | assert response.status_code == 200 118 | assert response.json() == {"id": 1, "url": "https://foo.bar"} 119 | 120 | 121 | def test_remove_summary_incorrect_id(test_app, monkeypatch): 122 | async def mock_get(id): 123 | return None 124 | 125 | monkeypatch.setattr(crud, "get", mock_get) 126 | 127 | response = test_app.delete("/summaries/999/") 128 | assert response.status_code == 404 129 | assert response.json()["detail"] == "Summary not found" 130 | 131 | 132 | def test_update_summary(test_app, monkeypatch): 133 | test_request_payload = {"url": "https://foo.bar", "summary": "updated"} 134 | test_response_payload = { 135 | "id": 1, 136 | "url": "https://foo.bar", 137 | "summary": "summary", 138 | "created_at": datetime.utcnow().isoformat(), 139 | } 140 | 141 | async def mock_put(id, payload): 142 | return test_response_payload 143 | 144 | monkeypatch.setattr(crud, "put", mock_put) 145 | 146 | response = test_app.put( 147 | "/summaries/1/", 148 | data=json.dumps(test_request_payload), 149 | ) 150 | assert response.status_code == 200 151 | assert response.json() == test_response_payload 152 | 153 | 154 | @pytest.mark.parametrize( 155 | "summary_id, payload, status_code, detail", 156 | [ 157 | [ 158 | 999, 159 | {"url": "https://foo.bar", "summary": "updated!"}, 160 | 404, 161 | "Summary not found", 162 | ], 163 | [ 164 | 0, 165 | {"url": "https://foo.bar", "summary": "updated!"}, 166 | 422, 167 | [ 168 | { 169 | "loc": ["path", "id"], 170 | "msg": "ensure this value is greater than 0", 171 | "type": "value_error.number.not_gt", 172 | "ctx": {"limit_value": 0}, 173 | } 174 | ], 175 | ], 176 | [ 177 | 1, 178 | {}, 179 | 422, 180 | [ 181 | { 182 | "loc": ["body", "url"], 183 | "msg": "field required", 184 | "type": "value_error.missing", 185 | }, 186 | { 187 | "loc": ["body", "summary"], 188 | "msg": "field required", 189 | "type": "value_error.missing", 190 | }, 191 | ], 192 | ], 193 | [ 194 | 1, 195 | {"url": "https://foo.bar"}, 196 | 422, 197 | [ 198 | { 199 | "loc": ["body", "summary"], 200 | "msg": "field required", 201 | "type": "value_error.missing", 202 | } 203 | ], 204 | ], 205 | ], 206 | ) 207 | def test_update_summary_invalid( 208 | test_app, monkeypatch, summary_id, payload, status_code, detail 209 | ): 210 | async def mock_put(id, payload): 211 | return None 212 | 213 | monkeypatch.setattr(crud, "put", mock_put) 214 | 215 | response = test_app.put(f"/summaries/{summary_id}/", data=json.dumps(payload)) 216 | assert response.status_code == status_code 217 | assert response.json()["detail"] == detail 218 | -------------------------------------------------------------------------------- /project/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "a68e4fcc9b2bba1c43650d82c96bd0707b31a89c40832506145b41f62e850e47" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aiosqlite": { 20 | "hashes": [ 21 | "sha256:1df802815bb1e08a26c06d5ea9df589bcb8eec56e5f3378103b0f9b223c6703c", 22 | "sha256:2e915463164efa65b60fd1901aceca829b6090082f03082618afca6fb9c8fdf7" 23 | ], 24 | "markers": "python_version >= '3.6'", 25 | "version": "==0.16.1" 26 | }, 27 | "asyncpg": { 28 | "hashes": [ 29 | "sha256:062e4ff80e68fe56066c44a8c51989a98785904bf86f49058a242a5887be6ce3", 30 | "sha256:0f4604a88386d68c46bf7b50c201a9718515b0d2df6d5e9ce024d78ed0f7189c", 31 | "sha256:1bbe5e829de506c743cbd5240b3722e487c53669a5f1e159abcc3b92a64a985e", 32 | "sha256:1d3efdec14f3fbcc665b77619f8b420564f98b89632a21694be2101dafa6bcf2", 33 | "sha256:1f514b13bc54bde65db6cd1d0832ae27f21093e3cb66f741e078fab77768971c", 34 | "sha256:2cb730241dfe650b9626eae00490cca4cfeb00871ed8b8f389f3a4507b328683", 35 | "sha256:2e3875c82ae609b21e562e6befdc35e52c4290e49d03e7529275d59a0595ca97", 36 | "sha256:348ad471d9bdd77f0609a00c860142f47c81c9123f4064d13d65c8569415d802", 37 | "sha256:3af9a8511569983481b5cf94db17b7cbecd06b5398aac9c82e4acb69bb1f4090", 38 | "sha256:82e23ba5b37c0c7ee96f290a95cbf9815b2d29b302e8b9c4af1de9b7759fd27b", 39 | "sha256:b37efafbbec505287bd1499a88f4b59ff2b470709a1d8f7e4db198d3e2c5a2c4", 40 | "sha256:ccd75cfb4710c7e8debc19516e2e1d4c9863cce3f7a45a3822980d04b16f4fdd", 41 | "sha256:d1cb6e5b58a4e017335f2a1886e153a32bd213ffa9f7129ee5aced2a7210fa3c", 42 | "sha256:e7a67fb0244e4a5b3baaa40092d0efd642da032b5e891d75947dab993b47d925", 43 | "sha256:f1df7cfd12ef484210717e7827cc2d4d550b16a1b4dd4566c93914c7a2259352" 44 | ], 45 | "index": "pypi", 46 | "version": "==0.22.0" 47 | }, 48 | "beautifulsoup4": { 49 | "hashes": [ 50 | "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35", 51 | "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25", 52 | "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666" 53 | ], 54 | "version": "==4.9.3" 55 | }, 56 | "certifi": { 57 | "hashes": [ 58 | "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", 59 | "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" 60 | ], 61 | "version": "==2020.12.5" 62 | }, 63 | "chardet": { 64 | "hashes": [ 65 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 66 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 67 | ], 68 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 69 | "version": "==4.0.0" 70 | }, 71 | "click": { 72 | "hashes": [ 73 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 74 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 75 | ], 76 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 77 | "version": "==7.1.2" 78 | }, 79 | "cssselect": { 80 | "hashes": [ 81 | "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf", 82 | "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc" 83 | ], 84 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 85 | "version": "==1.1.0" 86 | }, 87 | "fastapi": { 88 | "hashes": [ 89 | "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb", 90 | "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e" 91 | ], 92 | "index": "pypi", 93 | "version": "==0.63.0" 94 | }, 95 | "feedfinder2": { 96 | "hashes": [ 97 | "sha256:3701ee01a6c85f8b865a049c30ba0b4608858c803fe8e30d1d289fdbe89d0efe" 98 | ], 99 | "version": "==0.0.4" 100 | }, 101 | "feedparser": { 102 | "hashes": [ 103 | "sha256:1b00a105425f492f3954fd346e5b524ca9cef3a4bbf95b8809470e9857aa1074", 104 | "sha256:f596c4b34fb3e2dc7e6ac3a8191603841e8d5d267210064e94d4238737452ddd" 105 | ], 106 | "markers": "python_version >= '3.6'", 107 | "version": "==6.0.2" 108 | }, 109 | "filelock": { 110 | "hashes": [ 111 | "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", 112 | "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" 113 | ], 114 | "version": "==3.0.12" 115 | }, 116 | "gunicorn": { 117 | "hashes": [ 118 | "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", 119 | "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" 120 | ], 121 | "index": "pypi", 122 | "version": "==20.1.0" 123 | }, 124 | "h11": { 125 | "hashes": [ 126 | "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", 127 | "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" 128 | ], 129 | "markers": "python_version >= '3.6'", 130 | "version": "==0.12.0" 131 | }, 132 | "idna": { 133 | "hashes": [ 134 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 135 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 136 | ], 137 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 138 | "version": "==2.10" 139 | }, 140 | "iso8601": { 141 | "hashes": [ 142 | "sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79", 143 | "sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004" 144 | ], 145 | "version": "==0.1.14" 146 | }, 147 | "jieba3k": { 148 | "hashes": [ 149 | "sha256:980a4f2636b778d312518066be90c7697d410dd5a472385f5afced71a2db1c10" 150 | ], 151 | "version": "==0.35.1" 152 | }, 153 | "joblib": { 154 | "hashes": [ 155 | "sha256:9c17567692206d2f3fb9ecf5e991084254fe631665c450b443761c4186a613f7", 156 | "sha256:feeb1ec69c4d45129954f1b7034954241eedfd6ba39b5e9e4b6883be3332d5e5" 157 | ], 158 | "markers": "python_version >= '3.6'", 159 | "version": "==1.0.1" 160 | }, 161 | "lxml": { 162 | "hashes": [ 163 | "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d", 164 | "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3", 165 | "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2", 166 | "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f", 167 | "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927", 168 | "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3", 169 | "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7", 170 | "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f", 171 | "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade", 172 | "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468", 173 | "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b", 174 | "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4", 175 | "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83", 176 | "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04", 177 | "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791", 178 | "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51", 179 | "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1", 180 | "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a", 181 | "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f", 182 | "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee", 183 | "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec", 184 | "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969", 185 | "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28", 186 | "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a", 187 | "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa", 188 | "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106", 189 | "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d", 190 | "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4", 191 | "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0", 192 | "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4", 193 | "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2", 194 | "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0", 195 | "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654", 196 | "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2", 197 | "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23", 198 | "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586" 199 | ], 200 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 201 | "version": "==4.6.3" 202 | }, 203 | "newspaper3k": { 204 | "hashes": [ 205 | "sha256:44a864222633d3081113d1030615991c3dbba87239f6bbf59d91240f71a22e3e", 206 | "sha256:9f1bd3e1fb48f400c715abf875cc7b0a67b7ddcd87f50c9aeeb8fcbbbd9004fb" 207 | ], 208 | "index": "pypi", 209 | "version": "==0.2.8" 210 | }, 211 | "nltk": { 212 | "hashes": [ 213 | "sha256:240e23ab1ab159ef9940777d30c7c72d7e76d91877099218a7585370c11f6b9e", 214 | "sha256:57d556abed621ab9be225cc6d2df1edce17572efb67a3d754630c9f8381503eb" 215 | ], 216 | "markers": "python_version >= '3.5'", 217 | "version": "==3.6.2" 218 | }, 219 | "pillow": { 220 | "hashes": [ 221 | "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5", 222 | "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4", 223 | "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9", 224 | "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a", 225 | "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9", 226 | "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727", 227 | "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120", 228 | "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c", 229 | "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2", 230 | "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797", 231 | "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b", 232 | "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f", 233 | "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef", 234 | "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232", 235 | "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb", 236 | "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9", 237 | "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812", 238 | "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178", 239 | "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b", 240 | "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5", 241 | "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b", 242 | "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1", 243 | "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713", 244 | "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4", 245 | "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484", 246 | "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c", 247 | "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9", 248 | "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388", 249 | "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d", 250 | "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602", 251 | "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9", 252 | "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e", 253 | "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2" 254 | ], 255 | "markers": "python_version >= '3.6'", 256 | "version": "==8.2.0" 257 | }, 258 | "pydantic": { 259 | "hashes": [ 260 | "sha256:0c40162796fc8d0aa744875b60e4dc36834db9f2a25dbf9ba9664b1915a23850", 261 | "sha256:20d42f1be7c7acc352b3d09b0cf505a9fab9deb93125061b376fbe1f06a5459f", 262 | "sha256:2287ebff0018eec3cc69b1d09d4b7cebf277726fa1bd96b45806283c1d808683", 263 | "sha256:258576f2d997ee4573469633592e8b99aa13bda182fcc28e875f866016c8e07e", 264 | "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3", 265 | "sha256:2f2736d9a996b976cfdfe52455ad27462308c9d3d0ae21a2aa8b4cd1a78f47b9", 266 | "sha256:3114d74329873af0a0e8004627f5389f3bb27f956b965ddd3e355fe984a1789c", 267 | "sha256:3bbd023c981cbe26e6e21c8d2ce78485f85c2e77f7bab5ec15b7d2a1f491918f", 268 | "sha256:3bcb9d7e1f9849a6bdbd027aabb3a06414abd6068cb3b21c49427956cce5038a", 269 | "sha256:4bbc47cf7925c86a345d03b07086696ed916c7663cb76aa409edaa54546e53e2", 270 | "sha256:6388ef4ef1435364c8cc9a8192238aed030595e873d8462447ccef2e17387125", 271 | "sha256:830ef1a148012b640186bf4d9789a206c56071ff38f2460a32ae67ca21880eb8", 272 | "sha256:8fbb677e4e89c8ab3d450df7b1d9caed23f254072e8597c33279460eeae59b99", 273 | "sha256:c17a0b35c854049e67c68b48d55e026c84f35593c66d69b278b8b49e2484346f", 274 | "sha256:dd4888b300769ecec194ca8f2699415f5f7760365ddbe243d4fd6581485fa5f0", 275 | "sha256:dde4ca368e82791de97c2ec019681ffb437728090c0ff0c3852708cf923e0c7d", 276 | "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520", 277 | "sha256:e8bc082afef97c5fd3903d05c6f7bb3a6af9fc18631b4cc9fedeb4720efb0c58", 278 | "sha256:eb8ccf12295113ce0de38f80b25f736d62f0a8d87c6b88aca645f168f9c78771", 279 | "sha256:fb77f7a7e111db1832ae3f8f44203691e15b1fa7e5a1cb9691d4e2659aee41c4", 280 | "sha256:fbfb608febde1afd4743c6822c19060a8dbdd3eb30f98e36061ba4973308059e", 281 | "sha256:fff29fe54ec419338c522b908154a2efabeee4f483e48990f87e189661f31ce3" 282 | ], 283 | "markers": "python_full_version >= '3.6.1'", 284 | "version": "==1.8.1" 285 | }, 286 | "pypika-tortoise": { 287 | "hashes": [ 288 | "sha256:7176e98ff0cf7c311d4ba58f28f1755956265dee2f9781e65e1304a67a3e5aa5", 289 | "sha256:ec83b0b2964be01ef563f5f019b0332a18177604e841c47ad39d798798c6dfe9" 290 | ], 291 | "markers": "python_version >= '3.7' and python_version < '4.0'", 292 | "version": "==0.1.0" 293 | }, 294 | "python-dateutil": { 295 | "hashes": [ 296 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 297 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 298 | ], 299 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 300 | "version": "==2.8.1" 301 | }, 302 | "pytz": { 303 | "hashes": [ 304 | "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4", 305 | "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5" 306 | ], 307 | "version": "==2020.5" 308 | }, 309 | "pyyaml": { 310 | "hashes": [ 311 | "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", 312 | "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", 313 | "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", 314 | "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", 315 | "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", 316 | "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", 317 | "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", 318 | "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", 319 | "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", 320 | "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", 321 | "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", 322 | "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", 323 | "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", 324 | "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", 325 | "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", 326 | "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", 327 | "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", 328 | "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", 329 | "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", 330 | "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", 331 | "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", 332 | "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", 333 | "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", 334 | "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", 335 | "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", 336 | "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", 337 | "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", 338 | "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", 339 | "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" 340 | ], 341 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 342 | "version": "==5.4.1" 343 | }, 344 | "regex": { 345 | "hashes": [ 346 | "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5", 347 | "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79", 348 | "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31", 349 | "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500", 350 | "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11", 351 | "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14", 352 | "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3", 353 | "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439", 354 | "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c", 355 | "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82", 356 | "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711", 357 | "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093", 358 | "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a", 359 | "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb", 360 | "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8", 361 | "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17", 362 | "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000", 363 | "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d", 364 | "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480", 365 | "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc", 366 | "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0", 367 | "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9", 368 | "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765", 369 | "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e", 370 | "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a", 371 | "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07", 372 | "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f", 373 | "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac", 374 | "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7", 375 | "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed", 376 | "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968", 377 | "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7", 378 | "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2", 379 | "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4", 380 | "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87", 381 | "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8", 382 | "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10", 383 | "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29", 384 | "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605", 385 | "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6", 386 | "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042" 387 | ], 388 | "version": "==2021.4.4" 389 | }, 390 | "requests": { 391 | "hashes": [ 392 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 393 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 394 | ], 395 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 396 | "version": "==2.25.1" 397 | }, 398 | "requests-file": { 399 | "hashes": [ 400 | "sha256:07d74208d3389d01c38ab89ef403af0cfec63957d53a0081d8eca738d0247d8e", 401 | "sha256:dfe5dae75c12481f68ba353183c53a65e6044c923e64c24b2209f6c7570ca953" 402 | ], 403 | "version": "==1.5.1" 404 | }, 405 | "sgmllib3k": { 406 | "hashes": [ 407 | "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9" 408 | ], 409 | "version": "==1.0.0" 410 | }, 411 | "six": { 412 | "hashes": [ 413 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 414 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 415 | ], 416 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 417 | "version": "==1.15.0" 418 | }, 419 | "soupsieve": { 420 | "hashes": [ 421 | "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc", 422 | "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b" 423 | ], 424 | "markers": "python_version >= '3.0'", 425 | "version": "==2.2.1" 426 | }, 427 | "starlette": { 428 | "hashes": [ 429 | "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9", 430 | "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc" 431 | ], 432 | "markers": "python_version >= '3.6'", 433 | "version": "==0.13.6" 434 | }, 435 | "tinysegmenter": { 436 | "hashes": [ 437 | "sha256:ed1f6d2e806a4758a73be589754384cbadadc7e1a414c81a166fc9adf2d40c6d" 438 | ], 439 | "version": "==0.3" 440 | }, 441 | "tldextract": { 442 | "hashes": [ 443 | "sha256:cfae9bc8bda37c3e8c7c8639711ad20e95dc85b207a256b60b0b23d7ff5540ea", 444 | "sha256:e57f22b6d00a28c21673d2048112f1bdcb6a14d4711568305f6bb96cf5bb53a1" 445 | ], 446 | "markers": "python_version >= '3.5'", 447 | "version": "==3.1.0" 448 | }, 449 | "tortoise-orm": { 450 | "hashes": [ 451 | "sha256:1a742b2f15a31d47a8dea7706b478cc9a7ce9af268b61d77d0fa22cfbaea271a", 452 | "sha256:b0c02be3800398053058377ddca91fa051eb98eebb704d2db2a3ab1c6a58e347" 453 | ], 454 | "index": "pypi", 455 | "version": "==0.17.2" 456 | }, 457 | "tqdm": { 458 | "hashes": [ 459 | "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3", 460 | "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae" 461 | ], 462 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 463 | "version": "==4.60.0" 464 | }, 465 | "typing-extensions": { 466 | "hashes": [ 467 | "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", 468 | "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", 469 | "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" 470 | ], 471 | "version": "==3.10.0.0" 472 | }, 473 | "urllib3": { 474 | "hashes": [ 475 | "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", 476 | "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" 477 | ], 478 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", 479 | "version": "==1.26.4" 480 | }, 481 | "uvicorn": { 482 | "hashes": [ 483 | "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202", 484 | "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524" 485 | ], 486 | "index": "pypi", 487 | "version": "==0.13.4" 488 | } 489 | }, 490 | "develop": { 491 | "apipkg": { 492 | "hashes": [ 493 | "sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6", 494 | "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c" 495 | ], 496 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 497 | "version": "==1.5" 498 | }, 499 | "appdirs": { 500 | "hashes": [ 501 | "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", 502 | "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" 503 | ], 504 | "version": "==1.4.4" 505 | }, 506 | "attrs": { 507 | "hashes": [ 508 | "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", 509 | "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" 510 | ], 511 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 512 | "version": "==20.3.0" 513 | }, 514 | "bcrypt": { 515 | "hashes": [ 516 | "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", 517 | "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", 518 | "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", 519 | "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", 520 | "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", 521 | "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", 522 | "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" 523 | ], 524 | "markers": "python_version >= '3.6'", 525 | "version": "==3.2.0" 526 | }, 527 | "black": { 528 | "hashes": [ 529 | "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" 530 | ], 531 | "index": "pypi", 532 | "version": "==20.8b1" 533 | }, 534 | "certifi": { 535 | "hashes": [ 536 | "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", 537 | "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" 538 | ], 539 | "version": "==2020.12.5" 540 | }, 541 | "cffi": { 542 | "hashes": [ 543 | "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", 544 | "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", 545 | "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", 546 | "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", 547 | "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", 548 | "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", 549 | "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", 550 | "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", 551 | "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", 552 | "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", 553 | "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", 554 | "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", 555 | "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", 556 | "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", 557 | "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", 558 | "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", 559 | "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", 560 | "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", 561 | "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", 562 | "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", 563 | "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", 564 | "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", 565 | "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", 566 | "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", 567 | "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", 568 | "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", 569 | "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", 570 | "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", 571 | "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", 572 | "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", 573 | "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", 574 | "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", 575 | "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", 576 | "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", 577 | "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", 578 | "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", 579 | "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" 580 | ], 581 | "version": "==1.14.5" 582 | }, 583 | "chardet": { 584 | "hashes": [ 585 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 586 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 587 | ], 588 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 589 | "version": "==4.0.0" 590 | }, 591 | "click": { 592 | "hashes": [ 593 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 594 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 595 | ], 596 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 597 | "version": "==7.1.2" 598 | }, 599 | "coverage": { 600 | "hashes": [ 601 | "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", 602 | "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", 603 | "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", 604 | "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", 605 | "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", 606 | "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", 607 | "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", 608 | "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", 609 | "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", 610 | "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", 611 | "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", 612 | "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", 613 | "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", 614 | "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", 615 | "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", 616 | "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", 617 | "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", 618 | "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", 619 | "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", 620 | "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", 621 | "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", 622 | "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", 623 | "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", 624 | "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", 625 | "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", 626 | "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", 627 | "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", 628 | "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", 629 | "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", 630 | "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", 631 | "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", 632 | "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", 633 | "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", 634 | "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", 635 | "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", 636 | "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", 637 | "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", 638 | "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", 639 | "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", 640 | "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", 641 | "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", 642 | "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", 643 | "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", 644 | "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", 645 | "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", 646 | "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", 647 | "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", 648 | "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", 649 | "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", 650 | "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", 651 | "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", 652 | "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" 653 | ], 654 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 655 | "version": "==5.5" 656 | }, 657 | "cryptography": { 658 | "hashes": [ 659 | "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", 660 | "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", 661 | "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", 662 | "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", 663 | "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", 664 | "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", 665 | "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", 666 | "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", 667 | "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", 668 | "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", 669 | "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", 670 | "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" 671 | ], 672 | "markers": "python_version >= '3.6'", 673 | "version": "==3.4.7" 674 | }, 675 | "distro": { 676 | "hashes": [ 677 | "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92", 678 | "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799" 679 | ], 680 | "version": "==1.5.0" 681 | }, 682 | "docker": { 683 | "extras": [ 684 | "ssh" 685 | ], 686 | "hashes": [ 687 | "sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5", 688 | "sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd" 689 | ], 690 | "markers": "python_version >= '3.6'", 691 | "version": "==5.0.0" 692 | }, 693 | "docker-compose": { 694 | "hashes": [ 695 | "sha256:6510302727fe20faff5177f493b0c9897c73132539eaf55709a195c914c1a012", 696 | "sha256:d2064934f5084db8a0c4805e226447bf1fd0c928419be95afb6bd1866838c1f1" 697 | ], 698 | "index": "pypi", 699 | "version": "==1.29.1" 700 | }, 701 | "dockerpty": { 702 | "hashes": [ 703 | "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce" 704 | ], 705 | "version": "==0.4.1" 706 | }, 707 | "docopt": { 708 | "hashes": [ 709 | "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" 710 | ], 711 | "version": "==0.6.2" 712 | }, 713 | "execnet": { 714 | "hashes": [ 715 | "sha256:7a13113028b1e1cc4c6492b28098b3c6576c9dccc7973bfe47b342afadafb2ac", 716 | "sha256:b73c5565e517f24b62dea8a5ceac178c661c4309d3aa0c3e420856c072c411b4" 717 | ], 718 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 719 | "version": "==1.8.0" 720 | }, 721 | "flake8": { 722 | "hashes": [ 723 | "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378", 724 | "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a" 725 | ], 726 | "index": "pypi", 727 | "version": "==3.9.1" 728 | }, 729 | "idna": { 730 | "hashes": [ 731 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 732 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 733 | ], 734 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 735 | "version": "==2.10" 736 | }, 737 | "iniconfig": { 738 | "hashes": [ 739 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 740 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 741 | ], 742 | "version": "==1.1.1" 743 | }, 744 | "isort": { 745 | "hashes": [ 746 | "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", 747 | "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" 748 | ], 749 | "index": "pypi", 750 | "version": "==5.8.0" 751 | }, 752 | "jsonschema": { 753 | "hashes": [ 754 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 755 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 756 | ], 757 | "version": "==3.2.0" 758 | }, 759 | "mccabe": { 760 | "hashes": [ 761 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 762 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 763 | ], 764 | "version": "==0.6.1" 765 | }, 766 | "mypy-extensions": { 767 | "hashes": [ 768 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 769 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 770 | ], 771 | "version": "==0.4.3" 772 | }, 773 | "packaging": { 774 | "hashes": [ 775 | "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", 776 | "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" 777 | ], 778 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 779 | "version": "==20.9" 780 | }, 781 | "paramiko": { 782 | "hashes": [ 783 | "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898", 784 | "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035" 785 | ], 786 | "version": "==2.7.2" 787 | }, 788 | "pathspec": { 789 | "hashes": [ 790 | "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", 791 | "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" 792 | ], 793 | "version": "==0.8.1" 794 | }, 795 | "pluggy": { 796 | "hashes": [ 797 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 798 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 799 | ], 800 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 801 | "version": "==0.13.1" 802 | }, 803 | "py": { 804 | "hashes": [ 805 | "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", 806 | "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" 807 | ], 808 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 809 | "version": "==1.10.0" 810 | }, 811 | "pycodestyle": { 812 | "hashes": [ 813 | "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", 814 | "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" 815 | ], 816 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 817 | "version": "==2.7.0" 818 | }, 819 | "pycparser": { 820 | "hashes": [ 821 | "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", 822 | "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" 823 | ], 824 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 825 | "version": "==2.20" 826 | }, 827 | "pyflakes": { 828 | "hashes": [ 829 | "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", 830 | "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" 831 | ], 832 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 833 | "version": "==2.3.1" 834 | }, 835 | "pynacl": { 836 | "hashes": [ 837 | "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", 838 | "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", 839 | "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", 840 | "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", 841 | "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", 842 | "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", 843 | "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", 844 | "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", 845 | "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", 846 | "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", 847 | "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", 848 | "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", 849 | "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", 850 | "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", 851 | "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", 852 | "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", 853 | "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", 854 | "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" 855 | ], 856 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 857 | "version": "==1.4.0" 858 | }, 859 | "pyparsing": { 860 | "hashes": [ 861 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 862 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 863 | ], 864 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 865 | "version": "==2.4.7" 866 | }, 867 | "pyrsistent": { 868 | "hashes": [ 869 | "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" 870 | ], 871 | "markers": "python_version >= '3.5'", 872 | "version": "==0.17.3" 873 | }, 874 | "pytest": { 875 | "hashes": [ 876 | "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", 877 | "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" 878 | ], 879 | "index": "pypi", 880 | "version": "==6.2.3" 881 | }, 882 | "pytest-cov": { 883 | "hashes": [ 884 | "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7", 885 | "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da" 886 | ], 887 | "index": "pypi", 888 | "version": "==2.11.1" 889 | }, 890 | "pytest-docker": { 891 | "hashes": [ 892 | "sha256:7a0544dc00c83fd7808907a55835b1b7c07e3ea2d22ee558b5a233247e462735", 893 | "sha256:9faa9c87e6e0920612005c8d187c58b3d336f3af49a878f358578de125963858" 894 | ], 895 | "index": "pypi", 896 | "version": "==0.10.1" 897 | }, 898 | "pytest-forked": { 899 | "hashes": [ 900 | "sha256:6aa9ac7e00ad1a539c41bec6d21011332de671e938c7637378ec9710204e37ca", 901 | "sha256:dc4147784048e70ef5d437951728825a131b81714b398d5d52f17c7c144d8815" 902 | ], 903 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 904 | "version": "==1.3.0" 905 | }, 906 | "pytest-xdist": { 907 | "hashes": [ 908 | "sha256:2447a1592ab41745955fb870ac7023026f20a5f0bfccf1b52a879bd193d46450", 909 | "sha256:718887296892f92683f6a51f25a3ae584993b06f7076ce1e1fd482e59a8220a2" 910 | ], 911 | "index": "pypi", 912 | "version": "==2.2.1" 913 | }, 914 | "python-dotenv": { 915 | "hashes": [ 916 | "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544", 917 | "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f" 918 | ], 919 | "version": "==0.17.1" 920 | }, 921 | "pyyaml": { 922 | "hashes": [ 923 | "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", 924 | "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", 925 | "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", 926 | "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", 927 | "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", 928 | "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", 929 | "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", 930 | "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", 931 | "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", 932 | "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", 933 | "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", 934 | "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", 935 | "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", 936 | "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", 937 | "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", 938 | "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", 939 | "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", 940 | "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", 941 | "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", 942 | "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", 943 | "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", 944 | "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", 945 | "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", 946 | "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", 947 | "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", 948 | "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", 949 | "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", 950 | "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", 951 | "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" 952 | ], 953 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 954 | "version": "==5.4.1" 955 | }, 956 | "regex": { 957 | "hashes": [ 958 | "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5", 959 | "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79", 960 | "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31", 961 | "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500", 962 | "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11", 963 | "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14", 964 | "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3", 965 | "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439", 966 | "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c", 967 | "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82", 968 | "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711", 969 | "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093", 970 | "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a", 971 | "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb", 972 | "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8", 973 | "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17", 974 | "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000", 975 | "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d", 976 | "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480", 977 | "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc", 978 | "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0", 979 | "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9", 980 | "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765", 981 | "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e", 982 | "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a", 983 | "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07", 984 | "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f", 985 | "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac", 986 | "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7", 987 | "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed", 988 | "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968", 989 | "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7", 990 | "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2", 991 | "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4", 992 | "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87", 993 | "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8", 994 | "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10", 995 | "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29", 996 | "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605", 997 | "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6", 998 | "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042" 999 | ], 1000 | "version": "==2021.4.4" 1001 | }, 1002 | "requests": { 1003 | "hashes": [ 1004 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 1005 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 1006 | ], 1007 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1008 | "version": "==2.25.1" 1009 | }, 1010 | "six": { 1011 | "hashes": [ 1012 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 1013 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 1014 | ], 1015 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1016 | "version": "==1.15.0" 1017 | }, 1018 | "texttable": { 1019 | "hashes": [ 1020 | "sha256:ce0faf21aa77d806bbff22b107cc22cce68dc9438f97a2df32c93e9afa4ce436", 1021 | "sha256:f802f2ef8459058736264210f716c757cbf85007a30886d8541aa8c3404f1dda" 1022 | ], 1023 | "version": "==1.6.3" 1024 | }, 1025 | "toml": { 1026 | "hashes": [ 1027 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 1028 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 1029 | ], 1030 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1031 | "version": "==0.10.2" 1032 | }, 1033 | "typed-ast": { 1034 | "hashes": [ 1035 | "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace", 1036 | "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff", 1037 | "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266", 1038 | "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528", 1039 | "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6", 1040 | "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808", 1041 | "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4", 1042 | "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363", 1043 | "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341", 1044 | "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04", 1045 | "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41", 1046 | "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e", 1047 | "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3", 1048 | "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899", 1049 | "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805", 1050 | "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c", 1051 | "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c", 1052 | "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39", 1053 | "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a", 1054 | "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3", 1055 | "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7", 1056 | "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f", 1057 | "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075", 1058 | "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0", 1059 | "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40", 1060 | "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428", 1061 | "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927", 1062 | "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3", 1063 | "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f", 1064 | "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65" 1065 | ], 1066 | "version": "==1.4.3" 1067 | }, 1068 | "typing-extensions": { 1069 | "hashes": [ 1070 | "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", 1071 | "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", 1072 | "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" 1073 | ], 1074 | "version": "==3.10.0.0" 1075 | }, 1076 | "urllib3": { 1077 | "hashes": [ 1078 | "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", 1079 | "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" 1080 | ], 1081 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", 1082 | "version": "==1.26.4" 1083 | }, 1084 | "websocket-client": { 1085 | "hashes": [ 1086 | "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663", 1087 | "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f" 1088 | ], 1089 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1090 | "version": "==0.58.0" 1091 | } 1092 | } 1093 | } 1094 | --------------------------------------------------------------------------------