From b91799a150035d5baf2da6713e772dad228d9d8d Mon Sep 17 00:00:00 2001 From: James Snow Date: Fri, 22 Oct 2021 12:21:49 -0500 Subject: [PATCH] V2 (#40) SDK version 2 This represents a substantial rewrite of the existing SDK. The broad brush strokes are that it replaces the flask based API with one which uses FastAPI, provides library methods for cryptography operations, adds a subset of the functionality provided by mindmeld and attempts to offer a better getting started experience. There have also been significant updates to the CLI to add commands for easier setup and testing. We're keeping the repository the same (name change pending) but will be publishing to a new package name (webex-skills) and starting with version 2.0 to avoid any tag conflicts in github. The previous version will be maintained on a v1 branch for a short period while we allow beta users time to transition to the new version. Co-authored-by: snow0x2d0 Co-authored-by: Juan Rodriguez --- .flake8 | 3 - NOTICE | 2 +- README.md | 1145 +++++++- docs/images/greeter_directory.png | Bin 0 -> 16107 bytes docs/images/smart_lights_dir_complete.png | Bin 0 -> 27313 bytes docs/images/smart_lights_directory.png | Bin 0 -> 18710 bytes docs/images/switch_directory.png | Bin 0 -> 10953 bytes get_started_documentation/README.md | 564 ---- .../echo_skill_secure/main.py | 155 -- .../echo-skill-secure/poetry.lock | 337 --- .../echo-skill-secure/pyproject.toml | 16 - .../secret_generator/main.py | 3 - .../echo-skill/echo_skill/main.py | 46 - .../echo-skill/poetry.lock | 229 -- .../images/skills_architecture.png | Bin 23420 -> 0 bytes get_started_documentation/openapi.json | 777 ------ poetry.lock | 2319 ++++++----------- pylintrc | 384 --- pyproject.toml | 41 +- tests/conftest.py | 45 +- tests/skill/README.md | 1 - tests/skill/__init__.py | 35 - tests/skill/__main__.py | 9 - tests/skill/domains/general/exit/test.txt | 15 - tests/skill/domains/general/exit/train.txt | 155 -- tests/skill/domains/general/greet/test.txt | 47 - tests/skill/domains/general/greet/train.txt | 509 ---- tests/skill/domains/general/help/test.txt | 10 - tests/skill/domains/general/help/train.txt | 96 - tests/skill/id_rsa.pem | 52 - tests/skill/id_rsa.pub | 14 - tests/test_crypto.py | 9 +- tests/test_dialogue.py | 79 + tests/test_directives.py | 50 + tests/test_responder.py | 120 - tests/test_skill_server.py | 80 - tox.ini | 17 - webex_assistant_sdk/__init__.py | 2 - webex_assistant_sdk/_version.py | 3 - webex_assistant_sdk/app.py | 49 - webex_assistant_sdk/cli.py | 152 -- webex_assistant_sdk/crypto.py | 126 - webex_assistant_sdk/dialogue.py | 251 -- webex_assistant_sdk/exceptions.py | 34 - webex_assistant_sdk/helpers.py | 153 -- webex_assistant_sdk/server.py | 134 - .../mindmeld_template/cookiecutter.json | 6 - .../docker-compose.yml | 22 - .../requirements.txt | 2 - .../{{cookiecutter.skill_name}}/__init__.py | 4 - .../{{cookiecutter.skill_name}}/__main__.py | 8 - .../{{cookiecutter.skill_name}}/root.py | 37 - .../__init__.py | 0 webex_skills/api/__init__.py | 3 + webex_skills/api/base.py | 58 + webex_skills/api/middlewares/__init__.py | 1 + webex_skills/api/middlewares/base.py | 28 + webex_skills/api/middlewares/decryption.py | 55 + webex_skills/api/middlewares/signing.py | 55 + webex_skills/api/mindmeld.py | 47 + webex_skills/api/simple.py | 23 + webex_skills/cli/__init__.py | 23 + webex_skills/cli/config.py | 27 + webex_skills/cli/crypto.py | 40 + webex_skills/cli/helpers.py | 11 + webex_skills/cli/nlp.py | 51 + webex_skills/cli/project.py | 141 + webex_skills/cli/remote.py | 96 + webex_skills/cli/skill.py | 164 ++ webex_skills/crypto/__init__.py | 5 + webex_skills/crypto/generation.py | 35 + .../crypto/messages.py | 62 +- webex_skills/crypto/signatures.py | 23 + .../dialogue}/__init__.py | 0 webex_skills/dialogue/manager.py | 100 + webex_skills/dialogue/responses.py | 80 + webex_skills/dialogue/rules.py | 63 + webex_skills/exceptions.py | 6 + .../config.py => webex_skills/logging.py | 0 .../models/__init__.py | 0 webex_skills/models/http.py | 23 + webex_skills/models/mindmeld.py | 50 + webex_skills/settings.py | 17 + webex_skills/static/default_app.py | 19 + .../default_domains}/greeting/exit/train.txt | 0 .../default_domains}/greeting/greet/train.txt | 0 webex_skills/static/env.tmpl | 5 + webex_skills/static/mm_app.py | 19 + .../static/pyproject.toml.tmpl | 11 +- webex_skills/supress_warnings.py | 17 + webex_skills/types.py | 13 + 91 files changed, 3353 insertions(+), 6365 deletions(-) delete mode 100644 .flake8 create mode 100644 docs/images/greeter_directory.png create mode 100644 docs/images/smart_lights_dir_complete.png create mode 100644 docs/images/smart_lights_directory.png create mode 100644 docs/images/switch_directory.png delete mode 100644 get_started_documentation/README.md delete mode 100755 get_started_documentation/echo-skill-secure/echo_skill_secure/main.py delete mode 100644 get_started_documentation/echo-skill-secure/poetry.lock delete mode 100644 get_started_documentation/echo-skill-secure/pyproject.toml delete mode 100644 get_started_documentation/echo-skill-secure/secret_generator/main.py delete mode 100755 get_started_documentation/echo-skill/echo_skill/main.py delete mode 100644 get_started_documentation/echo-skill/poetry.lock delete mode 100644 get_started_documentation/images/skills_architecture.png delete mode 100644 get_started_documentation/openapi.json delete mode 100644 pylintrc delete mode 100644 tests/skill/README.md delete mode 100644 tests/skill/__init__.py delete mode 100644 tests/skill/__main__.py delete mode 100644 tests/skill/domains/general/exit/test.txt delete mode 100644 tests/skill/domains/general/exit/train.txt delete mode 100644 tests/skill/domains/general/greet/test.txt delete mode 100644 tests/skill/domains/general/greet/train.txt delete mode 100644 tests/skill/domains/general/help/test.txt delete mode 100644 tests/skill/domains/general/help/train.txt delete mode 100644 tests/skill/id_rsa.pem delete mode 100644 tests/skill/id_rsa.pub create mode 100644 tests/test_dialogue.py create mode 100644 tests/test_directives.py delete mode 100644 tests/test_responder.py delete mode 100644 tests/test_skill_server.py delete mode 100644 tox.ini delete mode 100644 webex_assistant_sdk/__init__.py delete mode 100644 webex_assistant_sdk/_version.py delete mode 100644 webex_assistant_sdk/app.py delete mode 100644 webex_assistant_sdk/cli.py delete mode 100644 webex_assistant_sdk/crypto.py delete mode 100644 webex_assistant_sdk/dialogue.py delete mode 100644 webex_assistant_sdk/exceptions.py delete mode 100644 webex_assistant_sdk/helpers.py delete mode 100644 webex_assistant_sdk/server.py delete mode 100644 webex_assistant_sdk/templates/mindmeld_template/cookiecutter.json delete mode 100644 webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/docker-compose.yml delete mode 100644 webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/requirements.txt delete mode 100644 webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__init__.py delete mode 100644 webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__main__.py delete mode 100644 webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/root.py rename {get_started_documentation/echo-skill-secure/echo_skill_secure => webex_skills}/__init__.py (100%) mode change 100755 => 100644 create mode 100644 webex_skills/api/__init__.py create mode 100644 webex_skills/api/base.py create mode 100644 webex_skills/api/middlewares/__init__.py create mode 100644 webex_skills/api/middlewares/base.py create mode 100644 webex_skills/api/middlewares/decryption.py create mode 100644 webex_skills/api/middlewares/signing.py create mode 100644 webex_skills/api/mindmeld.py create mode 100644 webex_skills/api/simple.py create mode 100644 webex_skills/cli/__init__.py create mode 100644 webex_skills/cli/config.py create mode 100644 webex_skills/cli/crypto.py create mode 100644 webex_skills/cli/helpers.py create mode 100644 webex_skills/cli/nlp.py create mode 100644 webex_skills/cli/project.py create mode 100644 webex_skills/cli/remote.py create mode 100644 webex_skills/cli/skill.py create mode 100644 webex_skills/crypto/__init__.py create mode 100644 webex_skills/crypto/generation.py rename get_started_documentation/echo-skill-secure/echo_skill_secure_tester/main.py => webex_skills/crypto/messages.py (57%) create mode 100644 webex_skills/crypto/signatures.py rename {get_started_documentation/echo-skill/echo_skill => webex_skills/dialogue}/__init__.py (100%) mode change 100755 => 100644 create mode 100644 webex_skills/dialogue/manager.py create mode 100644 webex_skills/dialogue/responses.py create mode 100644 webex_skills/dialogue/rules.py create mode 100644 webex_skills/exceptions.py rename tests/skill/config.py => webex_skills/logging.py (100%) rename tests/skill/entities/.gitkeep => webex_skills/models/__init__.py (100%) create mode 100644 webex_skills/models/http.py create mode 100644 webex_skills/models/mindmeld.py create mode 100644 webex_skills/settings.py create mode 100644 webex_skills/static/default_app.py rename {webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/domains => webex_skills/static/default_domains}/greeting/exit/train.txt (100%) rename {webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/domains => webex_skills/static/default_domains}/greeting/greet/train.txt (100%) create mode 100644 webex_skills/static/env.tmpl create mode 100644 webex_skills/static/mm_app.py rename get_started_documentation/echo-skill/pyproject.toml => webex_skills/static/pyproject.toml.tmpl (52%) create mode 100644 webex_skills/supress_warnings.py create mode 100644 webex_skills/types.py diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 494cbd4..0000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 100 -ignore = E203,W503 diff --git a/NOTICE b/NOTICE index 6dcd18c..f5e2ea1 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Webex Assistant SDK -Copyright (c) 2020 Cisco Systems, Inc. and/or its affiliates +Copyright (c) 2021 Cisco Systems, Inc. and/or its affiliates This project includes software developed at Cisco Systems, Inc. and/or its affiliates. diff --git a/README.md b/README.md index 4671d3a..40b6aca 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,1139 @@ -An SDK for developing Webex Assistant Skills based on the [MindMeld](https://www.mindmeld.com) platform. +# Webex Skills SDK -## Install the SDK +The Webex Skills SDK is designed to simplify the process of creating a Webex Assistant Skill. +It provides some tools that help to easily set up a template skill, deal with encryption and +test the skill locally, among other things. -`pip install webex_assistant_sdk` +In this document we'll go through some examples of how to use this SDK to create different types +of skills, and we'll also show how to use the different tools available. -## Using the SDK +## Overview -To use the SDK we just need to import SkillApplication and pass in the RSA private key as well as the secret for verifying the request's header. +In this documentation we are going to look at the following topics: -Here is an example implementation which is found in the `tests` folder: +- [Requirements](#requirements) + - [Installing the SDK](#installing-the-sdk) +- [Simple Skills vs MindMeld Skills](#simple-skills-vs-mindmeld-skills) + - [Simple Skills](#simple-skills) + - [MindMeld Skills](#mindmeld-skills) +- [Building a Simple Skill](#building-a-simple-skill) + - [Create the Skill Template](#create-the-skill-template) + - [Running the Template](#running-the-template) + - [Checking the Skill](#checking-the-skill) + - [Invoking the Skill](#invoking-the-skill) + - [Updating the Skill](#updating-the-skill) + - [More About the Simple Skill Handler Signature](#more-about-the-simple-skill-handler-signature) +- [Building a MindMeld Skill](#building-a-mindmeld-skill) + - [Invoking the MindMeld Skill](#invoking-the-mindMeld-skill) + - [Building New Models](#building-new-models) + - [Testing the Models](#testing-the-models) + - [More About the MindMelmd Skill Handler Signature](#more-about-the-mindmeld-skill-handler-signature) +- [Converting a Simple Skill into a MindMeld Skill](#converting-a-simple-skill-into-a-mindmeld-skill) + - [Adding the Training Data](#adding-the-training-data) + - [Updating the Handlers](#updating-the-handlers) + - [Testing the Skill](#testing-the-skill) +- [Encryption](#encryption) + - [Generating Secrets](#generating-secrets) + - [Generating Keys](#generating-keys) +- [Remotes](#remotes) + - [Creating a Remote](#creating-a-remote) + - [Listing Remotes](#listing-remotes) +- [Further Development and Deployment of your Skill](#further-development-and-deployment-of-your-skill) + +## Requirements + +In order to follow the examples in this guide, we'll need to install the SDK and its dependencies. Right +now the SDK works with Python 3.7 and above. Note that if you want to build a `MindMeld Skill` as shown +later in the guide you will have to use either Python 3.7 or 3.8, since those are the only supported versions +for the `MindMeld Library`. + +### Installing the SDK + +For this guide, we assume you are familiar and have installed: +- [pyenv](https://github.com/pyenv/pyenv) +- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) +- [pip](https://pypi.org/project/pip/) +- [Poetry](https://python-poetry.org/) + +We'll start by creating a virtual environment: + +```bash +pyenv install 3.8.6 +pyenv virtualenv 3.8.6 webex-skills +pyenv local webex-skills +``` + +We can now install the SDK using `pip`: + +```bash +pip install webex-skills +``` + +We should be all set, we'll use the SDK later in this guide. You will need to work inside the `webex-skills` virtual +environment we just created. + +## Simple Skills vs MindMeld Skills + +In a nutshell, a skill is a web service that takes a request containing some text and some context, +analyzes that information and responds accordingly. The level of analysis done on that information +will depend greatly on the tasks the skills has to accomplish. Some skills will simply need to look +for keywords in the text, while others will perform complex NLP in order to understand what the user +is requesting. + +This SDK has tooling for creating 2 types of skills: `Simple Skills` and `MindMed Skills`. These should +serve as templates for basic and complex skills. Let's now take a look at these templates in detail. + +### Simple Skills + +`Simple Skills` do not perform any type of ML or NLP analysis on the requests. These skills are a good +starting point for developers to start tinkering with, and they are usually good enough for performing +trivial non-complicated tasks. Most developers would start with a `Simple Skill` and then migrate to a +`MindMed Skill` if needed. + +Most of the time all you need is to recognize a few keywords in the text. Imagine a skill which only task +is to turn on and off the lights in the office. Some typical queries would be: + +- "Turn on the lights" +- "Turn off the lights" +- "Turn on the lights please" +- "Turn the lights off" + +In this particular case, it will probably be good enough to just look for the words `on` and `off` in the +text received. If `on` is present, the skill turns on the lights and responds accordingly and vice versa. + +As you can imagine, we don't really need any complex NLP for this skill. A simple regex would be more +than enough. `Simple Skills` do just that: they provide a template where you can specify the regexes you +care about and have them map to specific handlers (`turn_on_lights` and `turn_off_lights` in our example). + +We'll build a simple skill in [this section](#building-a-simple-skill) + +### MindMeld Skills + +`MindMeld Skills` perform NLP analysis on the requests. These skills are a good template for cases where the +queries will have a lot of variation and contain a lot of information. + +Let's take the case of a skill for ordering food. Queries for a skill like this might look like the following: + +- "Order a pepperoni pizza from Super Pizzas" +- "Order a pad thai from Thailand Cafe" +- "I want a hamburger with fries and soda from Hyper Burgers" + +As we can see, using regexes for these cases can get out of hand really fast. We would need to be able to +recognize every single dish from every single restaurant, which might account for hundreds or thousands of regexes. +As we add more dishes and restaurants, updating the codebase becomes a real problem. + +For cases like this, we leverage the open source [MindMeld Library](https://www.mindmeld.com/). This library makes +it really easy to perform NLP on any text query and identify entities like `dishes`, `restaurants` and `quantities`. +With that, performing the required actions becomes a much easier job. + +We'll build a MindMeld skill in [this section](#building-a-mindmeld-skill) + +## Building a Simple Skill + +Let's now use the SDK to build a `Simple Skill`. As in the example above, we'll build a skill to turn lights on and +off according to what the user is asking. We are going to call this skill `Switch`. + +### Create the Skill Template + +In the `pyenv` environment we created before, run the following command: + +```bash +webex-skills project init switch +``` + +This will create a template for a simple skill. You should see the following file structure: + +![File Structure](docs/images/switch_directory.png) + +As you can see, the `project` section `init` command creates a template of a skill. As usual, you can use the `--help +` option to see the documentation for this command: + +```bash +$ webex-skills project init --help +Usage: webex-skills project init [OPTIONS] SKILL_NAME + + Create a new skill project from a template + +Arguments: + SKILL_NAME The name of the skill you want to create [required] + +Options: + --skill-path DIRECTORY Directory in which to initialize a skill project + [default: .] + + --secret TEXT A secret for encryption. If not provided, one + will be generated automatically. + + --mindmeld / --no-mindmeld If flag set, a MindMeld app will be created, + otherwise it defaults to a simple app [default: + False] + + --help Show this message and exit. +``` + +### Running the Template + +We can now run our skill and start testing it. There are a couple ways you can run it. + +First this SDK has a `run` command, you can run it as follows: + +```bash +webex-skills skills run switch +``` + +You should see an output similar to: +```bash +INFO: Started server process [86661] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit) +``` + +As usual, you can use the `--help` option to see the documentation for this command: + +```bash +$ webex-skills skills run --help +Usage: webex-skills skills run [OPTIONS] SKILL_NAME + +Arguments: + SKILL_NAME The name of the skill to run. [required] + +Options: + --help Show this message and exit. +``` + +The second option to run a skill is to use `uvicorn`. After all, the skill created is an `asgi` application based on +[FastAPI](https://fastapi.tiangolo.com/): + +```bash +uvicorn switch.main:api --port 8080 --reload +``` + +You should see an output similar to the following: + +```bash +INFO: Will watch for changes in these directories: [''] +INFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit) +INFO: Started reloader process [86234] using statreload +INFO: Started server process [86253] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +Now that we have the skill actually run it, we can test it. + +### Checking the Skill + +One quick thing we can do before sending actual requests to the skill is to make sure we have everything correctly +setup. The sdk provides a tool for that. We can call it as: + +```bash +webex-skills skills check switch +``` + +In your skill output, you should see something like this: +```bash +INFO: 127.0.0.1:58112 - "GET /check?signature=%3D HTTP/1.1" 200 OK +``` + +In the SDK output you should see: + +```bash +switch appears to be working correctly +``` + +That means that your skill is running and the `check` request was successfully processed. + +### Invoking the Skill + +The SDK `skills` section has an `invoke` command which is used for sending requests to the skill. With the skill +running, we can invoke it as follows: + +```bash +webex-skills skills invoke switch +``` + +We can now enter a command and see a response: +```bash +$ webex-skills skills invoke switch +Enter commands below (Ctl+C to exit) +>> hi +{ 'challenge': 'a129d633075c9c227cc4bdcd1653b063b6dfe613ca50355fa84e852dde4b198f', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Hello I am a super simple skill'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Hello I am a super simple skill'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881359}, + 'text': 'hi'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881359}} +``` + +We can see that we got all the directives back. The template skill will simply repeat or echo everything we send to it. + +As usual, you can use the `--help` option to see the documentation for this command: + +```bash +$ webex-skills skills invoke --help +Usage: webex-skills skills invoke [OPTIONS] [NAME] + + Invoke a skill running locally or remotely + +Arguments: + [NAME] The name of the skill to invoke. If none specified, you would need + to at least provide the `public_key_path` and `secret`. If + specified, all following configuration (keys, secret, url, ect.) + will be extracted from the skill. + + +Options: + -s, --secret TEXT The secret for the skill. If none provided you + will be asked for it. + + -k, --key PATH The path of the public key for the skill. + -u TEXT The public url for the skill. + -v Set this flag to get a more verbose output. + --encrypt / --no-encrypt Flag to specify if the skill is using encryption. + [default: True] + + --help Show this message and exit. +``` + +### Updating the Skill + +Let's now modify our skill so it does what we want: remember we want this skill to turn on and off the office lights. + +Simply update the `main.py` file with the following 2 handlers: ```python -from pathlib import Path +@api.handle(pattern=r'.*\son\s?.*') +async def turn_on(current_state: DialogueState) -> DialogueState: + new_state = current_state.copy() + + text = 'Ok, turning lights on.' + + # Call lights API to turn on your light here. + + new_state.directives = [ + responses.Reply(text), + responses.Speak(text), + responses.Sleep(10), + ] + + return new_state -from webex_assistant_sdk.app import SkillApplication -from webex_assistant_sdk.crypto import load_private_key_from_file +@api.handle(pattern=r'.*\soff\s?.*') +async def turn_off(current_state: DialogueState) -> DialogueState: + new_state = current_state.copy() -secret = 'some secret' -key = load_private_key_from_file(str(Path(__file__).resolve().parent / 'id_rsa')) -app = SkillApplication(__name__, secret=secret, private_key=key) + text = 'Ok, turning lights off.' + + # Call lights API to turn off your light here. + + new_state.directives = [ + responses.Reply(text), + responses.Speak(text), + responses.Sleep(10), + ] -__all__ = ['app'] + return new_state ``` -Similar to MindMeld applications, for development convenience, we have included a Flask server for you to test your application. +The SDK provides the `@api.handle` decorator, and as you can see it can take a `pattern` parameter which is then +applied to the query to determine if the handler applies to the query being processed. This way, we can add +a few handlers by simple creating regexes that match the type of queries we want to support. -To run the development server you can use the `run` command: `python -m [app] run`. +In the example above, we have added regexes to identify the `on` and `off` keywords. Which mostly tell what the user +wants to do. -We do not recommend using the development server for production purpose. To learn more about productionizing Flask application, please check [Deployment Options](https://flask.palletsprojects.com/en/1.1.x/deploying/). +By using the `skill invoke` command we can run a few tests: -### The introduce decorator +```bash +>> turn on the lights +{ 'challenge': '56094568e18c66cb89eca8eb092cc3bbddcd64b4c0442300cfbe9af67183e260', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Ok, turning lights on.'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Ok, turning lights on.'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}, + 'text': 'turn on the lights'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}} +>> turn off the lights +{ 'challenge': '2587110a9c97ebf9ce435412c0ea6154eaef80f384ac829cbdc679db483e5beb', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Ok, turning lights off.'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Ok, turning lights off.'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}, + 'text': 'turn on the lights'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}, + 'text': 'turn off the lights'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}} +>> turn the lights on +{ 'challenge': 'ce24a510f6a7025ac5e4cc51b082483bdbcda31836e2c3567780c231e5674c59', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Ok, turning lights on.'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Ok, turning lights on.'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}, + 'text': 'turn on the lights'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}, + 'text': 'turn off the lights'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}, + 'text': 'turn the lights on'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634881502}} +``` + +In the examples above, we can see the skill responding with the correct message. In a real skill, we would also call an +API to actually perform the action the user wants. + +### More About the Simple Skill Handler Signature + +When we created our `Simple Skill`, our handler had the following siganture: -The SkillApplication adds a `introduce` decorator in addition to MindMeld's build in decorator. This is used to mark the dialogue state to use when a user calls a skill without any command, i.e. "talk to " +```python +async def greet(current_state: DialogueState) -> DialogueState: +``` -#### Example +You can also update the handler to give you another parameter: ```python -@app.introduce -def introduction(request, responder): - pass +async def greet(current_state: DialogueState, query: str) -> DialogueState: ``` -### Debugging +The `query` string parameter will give you the query that was sent to the skill, this can be used in cases where you +need to further analyze the text. -To debug the server and turn off encryption/decryption, you can set the environment variable `WXA_SKILL_DEBUG` to be `True`. +## Building a MindMeld Skill -### Command Line +When a skill is very complex and needs to handle many commands, usually the best approach is to create a MindMeld +based skill. This will allow us to use NLP to better classify the request and extract important information we need +from it. We are not going to go very deep into how a MindMeld application works, but there are a lot of resources in the +official [MindMeld library](https://www.mindmeld.com/) site. -Installing the webex_assistant_sdk package adds a wxa_sdk command line application. Use the `-h` argument for help. +This SDK also has tooling in place for setting up a MindMeld based skill. For that, we can use the `project init` +command with the `--mindmeld` flag set. Let's create a skill called `greeter`: ```bash -Usage: wxa_sdk [OPTIONS] COMMAND [ARGS]... +webex-skills project init greeter --mindmeld +``` + +The folder structure should look like this: + +![File Structure](docs/images/greeter_directory.png) + + +### Invoking the MindMeld Skill + +With the `greeter` skill running, let's try invoking it using the SDK `skills invoke` command: + +```bash +$ poetry run webex-skills skills invoke greeter + +Enter commands below (Ctl+C to exit) +>> hi +{ 'challenge': 'a31ced06481293abd8cbbcffe72d712e996cf0ddfb56d981cd1ff9c1d9a46bfd', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Hello I am a super simple skill using NLP'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Hello I am a super simple skill using NLP'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634880182}, + 'text': 'hi'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634880182}} +``` + +As you can see, the response of a `MindMeld Skill` has the same shape as a `Simple Skill`, it's really just the internals +of the skill that change. + +### Building New Models + +Since `MindMeld Skills` use NLP, we need to retrain the ML models each time we modify the training data. The SDK +provides a the 'nlp build` command for this purpose: + +```bash +webex-skills nlp build greeter +``` + +After running this, the models will be refreshed with the latest training data. + +As usual, you can use the `--help` option to see the documentation for this command: + +```bash +$ webex-skills nlp build --help +Usage: webex-skills nlp build [OPTIONS] [NAME] + + Build nlp models associated with this skill + +Arguments: + [NAME] The name of the skill to build. Options: - --install-completion [bash|zsh|fish|powershell|pwsh] - Install completion for the specified shell. - --show-completion [bash|zsh|fish|powershell|pwsh] - Show completion for the specified shell, to - copy it or customize the installation. + --help Show this message and exit. +``` + +### Testing the Models - --help Show this message and exit. +Anothoer useful command in this SDK is the `nlp process` command. It's similar to the `skill invoke` command, in the +sense that it will send a query to the running skill. However, the query will only be run through the NLP pipeline so +we can see how it was categorized. Let's look at an example: + +```bash +$ webex-skills nlp process greeter -Commands: - generate-keys Generate an RSA keypair - generate-secret Generate a secret token for signing requests - invoke Invoke a skill running locally or remotely - new Create a new skill project from a template +Enter a query below (Ctl+C to exit) +>> hi +{'domain': 'greeting', 'entities': [], 'intent': 'greet', 'text': 'hi'} +>> ``` + +You can see that now the response only contains the extracted ANLP pieces. This command is very useful for testing +your models as you work on improving them. + +As usual, you can use the `--help` option to see the documentation for this command: + +```bash +$ webex-skills nlp process --help +Usage: webex-skills nlp process [OPTIONS] [NAME] + + Run a query through NLP processing + +Arguments: + [NAME] The name of the skill to send the query to. + +Options: + --help Show this message and exit. +``` + +### More About the MindMeld Skill Handler Signature + +When we created our `Simple Skill`, our handler had the following siganture: + +```python +async def greet(current_state: DialogueState) -> DialogueState: +``` + +You can also update the handler to give you another parameter: + +```python +async def greet(current_state: DialogueState, , processed_query: ProcessedQuery) -> DialogueState: +``` + +The `processed_query` parameter will give you the `text` that was sent to the skill, the `domain` and `intent` identified +as well as the `entities` extracted from the query. This can be useful in cases where you want to use the entities as +part of the skill logic. We'll show an example of this in the +[Converting a Simple Skill into a MindMeld Skill](#converting-a-simple-skill-into-a-mindMeld-skill) section. + +## Converting a Simple Skill into a MindMeld Skill + +In many cases, when you start creating a new skill, you would probably start with a `Simple Skill`. As your skill grows +in complexity, you might need to convert it into a `Mindmeld Skill`. This SDK makes that conversion very easy, we are +going to take a look into that next. + +Consider the [Switch Skill](#building-a-simple-skill) we built above. It really just recognizes if we want to turn +something on or off. But what if we have multiple lights? We could in principle look at the query and try to extract +the light we want to switch with another regex, but that can be very brittle. Instead, let's try turning it into a +`MindMeld Skill`. + +### Adding the Training Data + +Let's start by creating our domains, intents and entities. We still want to just turn on and off lights, but we want to +be able to identify which light to switch. On the `switch` app, create the following folder structure: + +![File Structure](docs/images/smart_lights_directory.png) + +As you can see, we have created a `greeting` and `smart_lights` domains, the intents `greet`, `turn_lights_on` +and `turn_lights_off`, and the entities `all` and `location`. We now need to add training data to make this setup work. + +Normally, you would need to collect training data manually to create your domain, intents and entities. But, for this +guide, we are going to take a shortcut. If you are familiar with the [MindMeld library](https://www.mindmeld.com/), you +have probably seen that it comes with `Blueprint` applications, so we are going to borrow some data from one of them. + +Go to this [repo](https://github.com/CiscoDevNet/mindmeld-blueprints/tree/develop/blueprints/home_assistant/domains) +and copy the `train.txt` files from the corresponding intents into our folders. Do the same for entities, from +[here](https://github.com/CiscoDevNet/mindmeld-blueprints/tree/develop/blueprints/home_assistant/entities) copy the +corresponding `gazetteer.txt` and `mapping.json` files into our folders. Our directory should now look like this: + +![File Structure](docs/images/smart_lights_dir_complete.png) + +### Updating the Handlers + +The next thing we'll do is to convert our logic to turn it into a `MindMeld Skill`. The steps are very simple, let's +start. We'll make all the following changes in `main.py`: + +Instead of making the variable `app` a `SimpleApi`, make it a `MindMeldAPI`: +```python +api = MindmeldAPI() +``` + +In the `@api.handle` decorators, add the intent you want to handle instead of the pattern: + +```python +@api.handle(intent='turn_lights_on') +async def turn_on(current_state: DialogueState, processed_query: ProcessedQuery) -> DialogueState: +... + +@api.handle(intent='turn_lights_off') +async def turn_off(current_state: DialogueState, processed_query: ProcessedQuery) -> DialogueState: +``` + +Finally, add some logic to complement the response with the location entity if available. In the `turn_on` handler, +replace the line: + +```python + text = 'Ok, turning lights on.' +``` + +With the following logic: + +```python + if len(processed_query.entities) > 0: + location = processed_query.entities[0]['text'] + if location == 'all': + text = 'Ok, turning all lights on.' + else: + text = f'Ok, turning the {location} lights on.' + else: + text = 'Ok, turning all lights on.' +``` + +Do the corresponding change to the `turn_off` handler. + +You will also need to import the new classes you are using: `MindmeldAPI` and `ProcessedQuery`. + +That's it! We now have NLP support in our skill. + +All in all your `main.py` should look like this: + +```python +from webex_assistant_sdk.api import MindmeldAPI +from webex_assistant_sdk.dialogue import responses +from webex_assistant_sdk.models.mindmeld import DialogueState, ProcessedQuery + +api = MindmeldAPI() + + +@api.handle(default=True) +async def greet(current_state: DialogueState) -> DialogueState: + text = 'Hello I am a super simple skill' + new_state = current_state.copy() + + new_state.directives = [ + responses.Reply(text), + responses.Speak(text), + responses.Sleep(10), + ] + + return new_state + + +@api.handle(intent='turn_lights_on') +async def turn_on(current_state: DialogueState, processed_query: ProcessedQuery) -> DialogueState: + new_state = current_state.copy() + + if len(processed_query.entities) > 0: + location = processed_query.entities[0]['text'] + if location == 'all': + text = 'Ok, turning all lights on.' + else: + text = f'Ok, turning the {location} lights on.' + else: + text = 'Ok, turning all lights on.' + + # Call lights API to turn on your light here. + + new_state.directives = [ + responses.Reply(text), + responses.Speak(text), + responses.Sleep(10), + ] + + return new_state + + +@api.handle(intent='turn_lights_off') +async def turn_off(current_state: DialogueState, processed_query: ProcessedQuery) -> DialogueState: + new_state = current_state.copy() + + if len(processed_query.entities) > 0: + location = processed_query.entities[0]['text'] + if location == 'all': + text = 'Ok, turning all lights off.' + else: + text = f'Ok, turning the {location} lights off.' + else: + text = 'Ok, turning all lights off.' + + # Call lights API to turn off your light here. + + new_state.directives = [ + responses.Reply(text), + responses.Speak(text), + responses.Sleep(10), + ] + + return new_state +``` + +### Testing the Skill + +We can now test the skill to make sure it works as intended. Since we just added our training data, we need to build +the models first. Since we have entities defined, we will need to have Elasticsearch running for the `nlp build` +command to work properly. You can refer to the +[MindMeld Getting Started Documentation](https://www.mindmeld.com/docs/userguide/getting_started.html) for a guide on +how to install and run Elasticsearch. With that out of the way, you can build the models: + +```bash +webex-skills nlp build switch +``` + +We can now run our new skill: + +```bash +webex-skills skills run switch +``` + +Finally, we can use the invoke method to send a couple commands: + +```bash +$ webex-skills skills invoke switch +Enter commands below (Ctl+C to exit) +>> hi +{ 'challenge': 'bd57973f82227c37fdaed9404f86be521ecdbefc684e15319f0d51bbecbb456e', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Hello I am a super simple skill'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Hello I am a super simple skill'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'hi'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}} +>> turn on the lights +{ 'challenge': 'ff8e57bcb94b90c736e11dd79ae5fe3b269dcc450d7bb07b083de73d9a22d5e8', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Ok, turning all lights on.'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Ok, turning all lights on.'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'hi'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'turn on the lights'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}} +>> turn off the kitchen lights +{ 'challenge': '300e7adfe2f199998f152793b944bc597c5145e991dda621e1495e2e06cebb6e', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Ok, turning the kitchen lights off.'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Ok, turning the kitchen lights off.'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'hi'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'turn on the lights'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'turn off the kitchen lights'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}} +>> turn off all the lights +{ 'challenge': 'e92a90c304c9ef96a3edf31c6ffb10606739cdf98ad34cfd57314b20138ad59b', + 'directives': [ {'name': 'reply', 'payload': {'text': 'Ok, turning all lights off.'}, 'type': 'action'}, + {'name': 'speak', 'payload': {'text': 'Ok, turning all lights off.'}, 'type': 'action'}, + {'name': 'sleep', 'payload': {'delay': 10}, 'type': 'action'}], + 'frame': {}, + 'history': [ { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'hi'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'turn on the lights'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'turn off the kitchen lights'}, + { 'context': {}, + 'directives': [], + 'frame': {}, + 'history': [], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}, + 'text': 'turn off all the lights'}], + 'params': { 'allowed_intents': [], + 'dynamic_resource': {}, + 'language': 'en', + 'locale': None, + 'target_dialogue_state': None, + 'time_zone': 'UTC', + 'timestamp': 1634934720}} +>> +``` + +We have now converted a `Simple Skill` into a `MindMeld Skill`. + +## Encryption + +Skills require encryption in order to safely send and receive requests. For the encryption to work properly, we need +to provide a key pair and a secret for our skills. As we saw in the [Simple Skill](#building-a-simple-skill) and +[MindMeld Skill](#building-a-mindMeld-skill) examples above, the keys and secret will be automatically created for us +when we use the SDK to create a template. However, the SDK also has tools to create these manually if we need to. + +These tools are under the `crypto` section which we'll try next. + +### Generating Secrets + +Generating a secret is very simple, simply use the `generate-secret` command: + +```bash +webex-skills crypto generate-secret +``` + +A secret will be logged to the terminal, which then you can add to your app. + +### Generating Keys + +Generating a key pair is very simple, simply use the `generate-keys` command: + +```bash +webex-skills crypto generate-keys +``` + +A key pair will be created. + +As usual, you can use the `--help` option to see the documentation for this command: + +```bash +$ webex-skills crypto generate-keys --help +Usage: webex-skills crypto generate-keys [OPTIONS] [FILEPATH] + + Generate an RSA keypair + +Arguments: + [FILEPATH] The path where to save the keys created. By default, they get + created in the current directory. + + +Options: + --name TEXT The name to use for the keys created. [default: id_rsa] + --help Show this message and exit. +``` + +## Remotes + +This SDK also has the notion of `remotes`. That is, skills that are already running and even deployed somewhere, but +we still want to be able to test them using the SDK. + +The process for using remotes is very simple, we're going to look into that now. We'll use the `remote` section of the +SDK. + +Remotes are automatically added for skills created with this SDK. + +### Creating a Remote + +You can create a remote by using the `create` command. Let's recreate a remote for a skill called `echo`: + +```bash +webex-skills remote create echo +``` + +Follow the prompts to create a new remote: + +```bash +$ webex-skills remote create echo + +Secret: +Public key path [id_rsa.pub]: +URL to invoke the skill [http://localhost:8080/parse]: +``` + +As usual, you can use the `--help` option to see the documentation for this command: + +```bash +$ webex-skills remote create --help + +Usage: webex-skills remote create [OPTIONS] NAME + + Add configuration for a new remote skill to the cli config file + +Arguments: + NAME The name to give to the remote. [required] + +Options: + -u TEXT URL of the remote. If not provided it will be requested. + -s, --secret TEXT The skill secret. If not provided it will be requested. + -k, --key PATH The path to the public key. If not provided it will be + requested. + + --help Show this message and exit. +``` + +### Listing Remotes + +You can also list the remotes you currently have set up. For that you can use the `list` command: + +```bash +webex-skills remote list +``` + +You will get something like: +```bash +{'echo': {'name': 'echo', + 'public_key_path': '', + 'secret': '', + 'url': 'http://localhost:8080/parse'}, + 'greeter': {'app_dir': '>', + 'name': 'greeter', + 'private_key_path': '', + 'project_path': '', + 'public_key_path': '', + 'secret': '', + 'url': 'http://localhost:8080/parse'}, + 'switch': {'app_dir': '>', + 'name': 'switch', + 'private_key_path': '', + 'project_path': '', + 'public_key_path': '', + 'secret': '', + 'url': 'http://localhost:8080/parse'}} +``` + +As usual, you can use the `--help` option to see the documentation for this command: + +```bash +$ run webex-skills remote list --help + +Usage: webex-skills remote list [OPTIONS] + + List configured remote skills + +Options: + --name TEXT The name of a particular skill to display. + --help Show this message and exit. +``` + +## Further Development and Deployment of your Skill + +You might have noticed that when you create a new skill, there is a `pyproject.toml` file that gets added to the +project. This file already contains the dependencies needed to run the skill independently (without using the SDK's +`skills run` command.) + +In order to run the skill independently, you can use [Poetry](https://python-poetry.org/) to manage your dependencies, +or you can also replace the `pyproject.toml` with a `requirements.txt` file if you want to use +[pip](https://pypi.org/project/pip/) instead. + +For deployment, the SDK builds the skill based on [FastAPI](https://fastapi.tiangolo.com/), which offers multiple +options for running an app in production. You can find more information in their +[Deployment Documentation](https://fastapi.tiangolo.com/deployment/). diff --git a/docs/images/greeter_directory.png b/docs/images/greeter_directory.png new file mode 100644 index 0000000000000000000000000000000000000000..05d9bf02f2e5dbd9fa9d0534b27051ce9ee3ea15 GIT binary patch literal 16107 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

ND}GR_WXoMFKw<@oY#Jc_m&ZXyU`3u5SxP$q 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 literal 0 HcmV?d00001 diff --git a/docs/images/smart_lights_dir_complete.png b/docs/images/smart_lights_dir_complete.png new file mode 100644 index 0000000000000000000000000000000000000000..7923b9c130dc2cd09488b504176cc9953a10abf3 GIT binary patch literal 27313 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

2$@d)lC|+1*0lGVv8fzV6o4 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 literal 0 HcmV?d00001 diff --git a/docs/images/smart_lights_directory.png b/docs/images/smart_lights_directory.png new file mode 100644 index 0000000000000000000000000000000000000000..187351bd885f6a7e15a2bbd538055efff9ce9ed0 GIT binary patch literal 18710 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 l`w$`^WC%cHcTOrrqlt^x0>i*S;~B~du3fb&Ux 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�t(LPl@DxoJ5hZN5Ufrn$eBW~&PB{%Ub zG!7gcAAK;`Zc^i77oiIP41RY*@GCF;)0vbW2>@1^0VO{Ck+I3Gf>e$zjY#1seRA4UJ@8V*-XL>plL(E2 zwmqwNRln;Nv9%fDs&B`LkPK8x+o=&HqFng5X4sRY>+6b*(c&liesVGvQ-uheQ>}ot zeT~b3`-RKRHXfdiU-A5AR8INWNAf+`QL1@7_37s~>WE!)oHONARShOG znm)9(q`l?HD$PAAEBuTNaxch}+aG^zy7YTG%G9KFX1@>R0QJn%$$(lBZ+n}|eu`~( ze*dvO!+?xV6Z~)se@%w)CP(FrlMoo=WX@|4CaBswSRY5s`$;vv=_GcB+k7u`0-lKW%DOq~?ndh#H`G|D$8 zd@iszt{NlN8agRU3@HB9!N(BnM!W48A>R=vk=F;)dt`wHFbvUnQ|cHem3S;N4u>S0rz*pn? zoOpX{RI_@ou2tIl&;~lH?e_3{yyZ)qR6;s~YV#1jZ^}m_>qa&+y3s#B9iq!3^4T1l u^Xt$Pf45VO8i_$g>Un<^)3m?5V)4I>NTL0T6#Qq$TtP-fx=PX{", # The org id of the user making the request - "userId": "", # The id of the user making the request - "userType": "user", # The user type - "supportedDirectives": [ # The list of directives supported by the client making the request - "sleep", - "listen", - "reply", - "speak" - ] -} -``` -- `params`: Contains information like time_zone, timestamp of the query, language, etc... One particular - field here is `target_dialogue_state` this can be used to tell us what the user intended to do. In this - particular case, if the field is equal to `skill_intro`, we need to return an introductory message from the - skill. -- `frame`: Contains information that needs to be preserved during multiple continuous interactions with - the skill. -- `history`: Contains the history of the conversation in a multi-turn interaction. - -Things like the `context`, `params`, `frame`, and `history` become more important in `Mindmeld` based -applications. - -### 2. Response payload - -Our skill needs to respond with something that the `Webex Assistant` can understand and act upon. In order -to make the assistant take any action, like show text on the screen or speak something back to the user, we -use what we call directives. You can see we send a list of directives in our response. - -The following are the supported directives right now: - -- `reply`: Shows some text in the screen. -```json -{ - "name": "reply", - "type": "view", - "payload": { - "text": "Hello!" - } -} -``` -- `speak`: Speaks the phrase using TTS. -```json -{ - "name": "speak", - "type": "action", - "payload": { - "text": "Hello!" - } -} -``` -- `listen`: Asks the assistant to listen and transcribe what the user is saying. -```json -{ - "name": "listen", - "type": "action" -} -``` -- `sleep`: Dismisses the assistant. -```json -{ - "name": "listen", - "type": "action", - "payload": { - "delay": 0 - } -} -``` -- `ui-hint`: Show a small hint at the bottom panel of the assistant to guide the user on things to say. -```json -{ - "name": "ui-hint", - "type": "view", - "payload": { - "text": ["Hello"], - "prompt": "Try saying", - "displayImmediately": false - } -} -``` -- `asr-hint`: Short phrases sent to the ASR engine to improve accuracy around the given vocabulary. -```json -{ - "name": "asr-hint", - "type": "action", - "payload": { - "text": ["Hello"] - } -} -``` -- `display-web-view`: Opens a web view with the specified URL. -```json -{ - "name": "display-web-view", - "type": "action", - "payload": { - "title": "Google", - "url": "https://google.com" - } -} -``` -- `clear-web-view`: Dismisses the web view. -```json -{ - "name": "clear-web-view", - "type": "action" -} -``` -- `assistant-event`: A generic event sent to the assistant, this can be used with macros on RoomOS devices. - Note that the inner `name` is required, and the inner `payload` has to be an object (or empty). -```json -{ - "name": "assistant-event", - "payload": { - "name": "test", - "payload": { "foo": "bar" }, - } -} -``` -So as you can see from the response, our skill is just telling the assistant to display and speak what the user -just said and then go to sleep. - -# Registering a Skill on the Skills Service - -Once you have a working skill, it needs to be deployed online in order for the `Skills Service` to reach it. - -Now, for security reasons, we are requiring a few things on deployed skills, to make sure the communication -between the `Skills Service` and the skill is as secure as possible: - -- Skills should run on HTTPS -- The request from the `Skills Service` will be encrypted and signed. The skill will be responsible for - decrypting the content and verifying the signature. -- The encrypted content will contain a `challenge` which is a string that needs to be sent back - to the service in the response. - -If you already have a deployed skill that fills the requirements above, you can jump to the section -[Create Skill on Skills Service](#create-skill-on-skills-service). If you are using the sample Skill in this tutorial, we'll show you how to -fill in those details next and then how register the skill. - -## Building Secure Echo Skill - -You probably noticed there is another version of the `Echo` skill in the repo under `echo-skill-secure`. This -version has the encryption pieces we need in order to register it with the `Skills Service`. We decided to -provide 2 versions of the skill so that in the simple 'insecure' version we built earlier you can focus more -on the API pieces and quickly test things up. However, a real skill will be closer to the 'secure' version -of `Echo` which we'll look into now. - -For starters, go into the `echo-skill-secure` folder and install its dependencies: - -```bash -cd echo-skill-secure -pyenv local 3.7.5 -poetry install -``` - -### Understanding the Encryption Flow - -As mentioned above, the data in the request to the skill will be encrypted. The encryption is done using rsa keys -that have to be provided by you, the developer, at the moment you register your skill with the service. You only -provide the public key to the service, and then use the private key in the skill to decrypt the content. - -In order for the service to know that the skill successfully decrypted the content, it includes a field in the -request called `challenge`. This is just a random string that needs to be included in the response from the skill. -If not included, the service will ignore the response and won't act on it. - -The final piece is for the skill to make sure the request comes from the official `Skills Service` and not from -some other entity trying to exploit the skill. The headers of the request contain a signature, which is basically -a hash of the request, that was created using a `secret` which also needs to be provided by you, the -developer, at the moment you register your skill with the service. Using this secret, the skill can create its -own signature from the request and make sure the signature sent by the service matches. - -With all the above the service verifies it's talking to the skill it's pretending to talk, and the skill can -verify the request comes from the official service. - -Let's now look into how this is all done in the `secure` version of our `Echo` skill. - -### Creating Secret and Keys - -As mentioned before, we'll need a rsa private/public key pair as well as a `secret`. Let's make those now. - -The `secret` is any string at least 22 characters long. You will have to create this yourself. Try following -the same rules you would follow to create a strong password. We have also provided a very simple tool -for creating a good secret under the `secret_generator` folder. If you want to use it, just do: - -```bash -poetry run python secret_generator/main.py -``` - -Now in order to create the keys, we recommend following [this guide](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key). -Make sure to create `rsa` type keys. - -Finally, export the created secret and keys to your environment so they can be picked up by the skill and the -tester latter: - -```bash -export ECHO_PUBLIC_KEY= -export ECHO_PRIVATE_KEY= -export ECHO_SECRET= -``` - -### Verifying the signature - -The first thing you should do when receiving a request is verifying the signature is valid. You can take a look at -how that's done on the secure version of `Echo`. Here's the method we use for signature verification: - -```python -def verify_signature(message: bytes, signature: bytes) -> None: - secret_bytes = SECRET.encode("utf-8") - - sig = hmac.HMAC(secret_bytes, hashes.SHA256()) - sig.update(message) - sig.verify(signature) -``` - -One thing to note here is that we are using the cryptography library, Python's stdlib includes a hmac library -which as a different interface. - -### Decrypting the Request's Message - -After the signature has been verified, you can follow to decrypt the content of the request. You can take a look at -how that's done on the secure version of `Echo`. Here's the method we use for decrypting the message: - -```python -def decrypt(message: str) -> str: - private_key_bytes = PRIVATE_KEY.encode("utf-8") - - private_key: RSAPrivateKey = load_ssh_private_key(private_key_bytes, None) - padding = OAEP(mgf=MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA256(), label=None) - - encrypted_fernet_key, fernet_token = message.split(".") - encrypted_fernet_key_bytes = base64.b64decode( - encrypted_fernet_key.encode("utf-8")) - - fernet_key = private_key.decrypt(encrypted_fernet_key_bytes, padding) - - fernet_token_bytes = base64.b64decode(fernet_token) - payload = Fernet(fernet_key).decrypt(fernet_token_bytes) - return payload.decode("utf-8") -``` - -### Running the Skill - -We run this skill similarly to how we did the `insecure` version: - -```bash -poetry run python echo_skill_secure/main.py -``` - -### Testing the Skill - -Since this skill is expecting an encrypted and signed request, it's not so easy to test. We have included a -simple tool to make testing a bit easier. You'll find this tool under the `echo_skill_secure_tester` directory. -At this point you can run this tool by simply doing: - -```bash -poetry run python echo_skill_secure_tester/main.py -``` - -Note the dependencies have been included in the general `echo-skill-secure` project, so there's no need to -install anything extra. - -If you take a look at the tester, you'll see that it's a very simple program that uses the skill's public key -and secret to sign and encrypt the request. It then sends the request and prints back the response from the -skill. - -You can also use this tool alone to test your own skills. - -Note that the skill can also take an environment variable: `ECHO_RUN_DEV_MODE`. When set to `true`, the skill skips -all the decryption and signing logic and basically becomes the `echo-skill` we tested first. This is a good -pattern to follow, so you can also test your skill in isolation from all the decryption and signature logic. - -### Health Endpoint - -You'll notice that in the secure Echo we also have a `GET` endpoint which is marked as a `health` endpoint. This -is not 100% required right now, but it's encouraged to add it to your apps since it will serve a purpose in -the upcoming `Developer Portal` we are putting together. - -The endpoint should have the same route as the normal invoke method for the skill, but it's a `GET` method instead. -The expected behaviour should be as follows: - -- There are 2 query params similar to the invocatrion endpoint: `signature` and `message`. -- The skill should verify these 2 params are included. -- The signature should be verified and the message decrypted. -- The decrypted message is a `challenge`, which should be sent back in the response to the `Skills Service`. - -## Hosting Sample Skill Locally - -Now that our skill is running securely, we can host it locally over https. For that we can use a service like -[localtunnel](https://localtunnel.github.io/www/). With the `secure` version of the `Echo` skill running, do: - -```bash -npm install -g localtunnel -lt --port 8080 -``` - -At this point we can go and create a new skill in the `Skills Service`. - -## Create Skill on Skills Service - -In this guide we are going to use the `Skills Service` API to do the creation of the skill manually. Later on -there will be more tools available to make this process easier. - -### Get your Token and Developer ID - -The simplest way to get this information is to login into [Webex for Developers](https://developer.webex.com/) and -then go to [this API endpoint](https://developer.webex.com/docs/api/v1/people/list-people). - -In that page, you will see that you can simply copy your Authorization token, which will be valid for 12 hours. - -If you don't know your developer/user id, you can also get it via this endpoint. Simple search for yourself -using your email and check the returned `id`. Note that this is base64 encoded, so you can go -[here](https://www.base64decode.org/) to decode it. The last part of the decoded URL will be your ID. -It should be a UUID. - -### Make the Create Skill Request - -Finally, we send a request to the service to create the skill. Using your tool of choice (curl, Postman, -Paw, etc...) make the following request (make sure to replace the values in <>): - -``` -POST https://assistant.us-east-2.intelligence.webex.com/skills/api/developers//skills -Authorization Bearer -{ - "name": "Echo", - "url": "", - "contact_email": "", - "languages": [ - "en" - ], - "secret": "", - "public_key": "" -} -``` - -Congratulations, at this point your skill is in the `Skills Service`! - -At this point the skill will only be enabled for yourself and not for other people in your organization. - -### Testing your Skill - -The simplest way to test your skill end to end is by using a Cisco RoomOS device, like a Roomkit or a Desk Pro -in personal mode that has the `Webex Assistant` enabled. - -As this is a beta feature, make sure that you have the `webex-assistant-skills` feature toggle set to true. - -# Further API Documentation - -For further documentation on how to use the API of the `Skills Service` you can take a look at our swagger -documentation. - -Simply go to their [online editor](https://editor.swagger.io/) and load the `openapi.json` file included in this -folder. You'll see the different available endpoints and more detailed description on how to use them. You can -even call the different endpoints from the editor. diff --git a/get_started_documentation/echo-skill-secure/echo_skill_secure/main.py b/get_started_documentation/echo-skill-secure/echo_skill_secure/main.py deleted file mode 100755 index 86b0ef6..0000000 --- a/get_started_documentation/echo-skill-secure/echo_skill_secure/main.py +++ /dev/null @@ -1,155 +0,0 @@ -import base64 -import json -import os - -from aiohttp import web -from cryptography.exceptions import InvalidSignature -from cryptography.fernet import Fernet -from cryptography.hazmat.primitives import hashes, hmac -from cryptography.hazmat.primitives.asymmetric.padding import MGF1, OAEP -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey -from cryptography.hazmat.primitives.serialization import load_pem_private_key - -DEV_MODE = os.getenv('ECHO_RUN_DEV_MODE', False) -PRIVATE_KEY = os.getenv('ECHO_PRIVATE_KEY') -SECRET = os.getenv('ECHO_SECRET') - - -routes = web.RouteTableDef() - - -def decrypt(message: str) -> str: - private_key_bytes = PRIVATE_KEY.encode("utf-8") - - private_key: RSAPrivateKey = load_pem_private_key(private_key_bytes, None) - padding = OAEP(mgf=MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) - - encrypted_fernet_key, fernet_token = message.split(".") - encrypted_fernet_key_bytes = base64.b64decode(encrypted_fernet_key.encode("utf-8")) - - fernet_key = private_key.decrypt(encrypted_fernet_key_bytes, padding) - - fernet_token_bytes = base64.b64decode(fernet_token) - payload = Fernet(fernet_key).decrypt(fernet_token_bytes) - return payload.decode("utf-8") - - -def verify_signature(message: bytes, signature: bytes) -> None: - secret_bytes = SECRET.encode("utf-8") - - sig = hmac.HMAC(secret_bytes, hashes.SHA256()) - sig.update(message) - sig.verify(signature) - - -def directive(name: str, dtype: str, payload: dict = None) -> dict: - return {'name': name, 'type': dtype, 'payload': payload or {}} - - -def build_response(text: str, challenge: str, should_listen: bool = False) -> dict: - response = { - 'directives': [ - directive('reply', 'view', {'text': text}), - directive('speak', 'action', {'text': text}), - directive('listen' if should_listen else 'sleep', 'action'), - ] - } - - if not DEV_MODE: - response['challenge'] = challenge - - return response - - -def handle_message(req_body: dict): - should_listen = False - if req_body.get('params', {}).get('target_dialogue_state') == 'skill_intro': - text = 'This is the echo skill. Say something and I will echo it back.' - should_listen = True - else: - text = req_body.get('text', "Hmm... I didn't get anything to echo") - if not isinstance(text, str): - # If it's a list of transcripts (this is what the actual Webex Asssitant sends) -- use first - text = text[0] - - return build_response(text, req_body.get('challenge', ''), should_listen) - - -# Health endpoint -@routes.get('/') -async def echo(request: web.BaseRequest) -> web.Response: - # Our signature and cipher bytes are expected to be base64 encoded byte strings - encoded_signature: str = request.query["signature"] - encoded_cipher: str = request.query["message"] - - # Bail on missing signature - if not encoded_signature: - return web.json_response({"error": "Missing signature"}, 400) - - # And on a missing message - if not encoded_cipher: - return web.json_response({"error": "Missing message"}, 400) - - # Convert our encoded signature and body to bytes - encoded_cipher_bytes: bytes = encoded_cipher.encode("utf-8") - - # We sign the encoded cipher text so we decode our signature, but not our cipher text yet - decoded_sig_bytes: bytes = base64.b64decode(encoded_signature) - - try: - # Cryptography's verify method throws rather than returning false. - verify_signature(encoded_cipher_bytes, decoded_sig_bytes) - except InvalidSignature: - return web.json_response({"error": "Invalid signature"}, 400) - - # Now that we've verified our signature we decode our cipher to get the raw bytes - decrypted_challenge = decrypt(encoded_cipher) - - return web.json_response({'challenge': decrypted_challenge, 'status': 'OK'}) - - -# Invoke endpoint -@routes.post('/') -async def echo(request: web.BaseRequest) -> web.Response: - if DEV_MODE: - req_body = await request.json() - else: - request_body: bytes = await request.json() - - # Our signature and cipher bytes are expected to be base64 encoded byte strings - encoded_signature: str = request_body.get("signature") - encoded_cipher: str = request_body.get("message") - - # Bail on missing signature - if not encoded_signature: - return web.json_response({"error": "Missing signature"}, 400) - - # And on a missing message - if not encoded_cipher: - return web.json_response({"error": "Missing message"}, 400) - - # Convert our encoded signature and body to bytes - encoded_cipher_bytes: bytes = encoded_cipher.encode("utf-8") - - # We sign the encoded cipher text so we decode our signature, but not our cipher text yet - decoded_sig_bytes: bytes = base64.b64decode(encoded_signature) - - try: - # Cryptography's verify method throws rather than returning false. - verify_signature(encoded_cipher_bytes, decoded_sig_bytes) - except InvalidSignature: - return web.json_response({"error": "Invalid signature"}, 400) - - # Now that we've verified our signature we decode our cipher to get the raw bytes - decrypted_body = decrypt(encoded_cipher) - - # The decrypted cipher text is a json string representing our MindMeld style message - req_body = json.loads(decrypted_body) - - response = handle_message(req_body) - return web.json_response(response) - - -app = web.Application() -app.add_routes(routes) -web.run_app(app) diff --git a/get_started_documentation/echo-skill-secure/poetry.lock b/get_started_documentation/echo-skill-secure/poetry.lock deleted file mode 100644 index 54ffb25..0000000 --- a/get_started_documentation/echo-skill-secure/poetry.lock +++ /dev/null @@ -1,337 +0,0 @@ -[[package]] -name = "aiohttp" -version = "3.7.4.post0" -description = "Async http client/server framework (asyncio)" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -async-timeout = ">=3.0,<4.0" -attrs = ">=17.3.0" -chardet = ">=2.0,<5.0" -multidict = ">=4.5,<7.0" -typing-extensions = ">=3.6.5" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["aiodns", "brotlipy", "cchardet"] - -[[package]] -name = "async-timeout" -version = "3.0.1" -description = "Timeout context manager for asyncio programs" -category = "main" -optional = false -python-versions = ">=3.5.3" - -[[package]] -name = "attrs" -version = "21.2.0" -description = "Classes Without Boilerplate" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] - -[[package]] -name = "cffi" -version = "1.14.6" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "cryptography" -version = "3.4.8" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] - -[[package]] -name = "idna" -version = "3.2" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "multidict" -version = "5.1.0" -description = "multidict implementation" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pycparser" -version = "2.20" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "typing-extensions" -version = "3.10.0.0" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "yarl" -version = "1.6.3" -description = "Yet another URL library" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[metadata] -lock-version = "1.1" -python-versions = "^3.9" -content-hash = "fc3ffa1271fe3eab7a635424eb4f7cb419cbcdfc3b950a3e599945183b1c6db7" - -[metadata.files] -aiohttp = [ - {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, - {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, -] -async-timeout = [ - {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, - {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, -] -attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -cffi = [ - {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, - {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, - {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, - {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, - {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, - {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, - {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, - {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, - {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, - {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, - {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, - {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, - {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, - {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, - {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, - {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, - {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, - {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, - {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] -cryptography = [ - {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"}, - {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"}, - {file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"}, - {file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e"}, - {file = "cryptography-3.4.8.tar.gz", hash = "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c"}, -] -idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, -] -multidict = [ - {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, - {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, - {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, - {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, - {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, - {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, - {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, - {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, - {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, - {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, - {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, - {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, - {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, -] -pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, -] -typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, -] -yarl = [ - {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, - {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, - {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, - {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, - {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, - {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, - {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, - {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, - {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, - {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, - {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, - {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, - {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, -] diff --git a/get_started_documentation/echo-skill-secure/pyproject.toml b/get_started_documentation/echo-skill-secure/pyproject.toml deleted file mode 100644 index df79157..0000000 --- a/get_started_documentation/echo-skill-secure/pyproject.toml +++ /dev/null @@ -1,16 +0,0 @@ -[tool.poetry] -name = "echo-skill-secure" -version = "0.1.0" -description = "" -authors = ["Juan Rodriguez "] - -[tool.poetry.dependencies] -python = "^3.7" -aiohttp = "^3.7.4" -cryptography = "^3.4.8" - -[tool.poetry.dev-dependencies] - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/get_started_documentation/echo-skill-secure/secret_generator/main.py b/get_started_documentation/echo-skill-secure/secret_generator/main.py deleted file mode 100644 index 6f04bb3..0000000 --- a/get_started_documentation/echo-skill-secure/secret_generator/main.py +++ /dev/null @@ -1,3 +0,0 @@ -import secrets - -print(secrets.token_urlsafe(16)) diff --git a/get_started_documentation/echo-skill/echo_skill/main.py b/get_started_documentation/echo-skill/echo_skill/main.py deleted file mode 100755 index d3ee962..0000000 --- a/get_started_documentation/echo-skill/echo_skill/main.py +++ /dev/null @@ -1,46 +0,0 @@ -from aiohttp import web - -routes = web.RouteTableDef() - - -def directive(name: str, dtype: str, payload: dict = None) -> dict: - return { - 'name': name, - 'type': dtype, - 'payload': payload or {} - } - - -def build_response(text: str, should_listen: bool = False) -> dict: - return { - 'directives': [ - directive('reply', 'view', {'text': text}), - directive('speak', 'action', {'text': text}), - directive('listen' if should_listen else 'sleep', 'action') - ] - } - - -def handle_message(req_body: dict): - should_listen = False - if req_body.get('params', {}).get('target_dialogue_state') == 'skill_intro': - text = 'This is the echo skill. Say something and I will echo it back.' - should_listen = True - else: - text = req_body.get('text', "Hmm... I didn't get anything to echo") - if not isinstance(text, str): - # If it's a list of transcripts (this is what the actual Webex Asssitant sends) -- use first - text = text[0] - - return build_response(text, should_listen) - - -@routes.post('/') -async def echo(request: web.BaseRequest) -> web.Response: - req_body = await request.json() - response = handle_message(req_body) - return web.json_response(response) - -app = web.Application() -app.add_routes(routes) -web.run_app(app) diff --git a/get_started_documentation/echo-skill/poetry.lock b/get_started_documentation/echo-skill/poetry.lock deleted file mode 100644 index c6a3737..0000000 --- a/get_started_documentation/echo-skill/poetry.lock +++ /dev/null @@ -1,229 +0,0 @@ -[[package]] -name = "aiohttp" -version = "3.7.4.post0" -description = "Async http client/server framework (asyncio)" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -async-timeout = ">=3.0,<4.0" -attrs = ">=17.3.0" -chardet = ">=2.0,<5.0" -multidict = ">=4.5,<7.0" -typing-extensions = ">=3.6.5" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["aiodns", "brotlipy", "cchardet"] - -[[package]] -name = "async-timeout" -version = "3.0.1" -description = "Timeout context manager for asyncio programs" -category = "main" -optional = false -python-versions = ">=3.5.3" - -[[package]] -name = "attrs" -version = "21.2.0" -description = "Classes Without Boilerplate" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] - -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "idna" -version = "3.2" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "multidict" -version = "5.1.0" -description = "multidict implementation" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "typing-extensions" -version = "3.10.0.0" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "yarl" -version = "1.6.3" -description = "Yet another URL library" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[metadata] -lock-version = "1.1" -python-versions = "^3.9" -content-hash = "00cfc167267ee0a3740ace7a2de533117db34435df0eb0d24fac392291a2767f" - -[metadata.files] -aiohttp = [ - {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, - {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, -] -async-timeout = [ - {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, - {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, -] -attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] -idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, -] -multidict = [ - {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, - {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, - {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, - {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, - {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, - {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, - {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, - {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, - {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, - {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, - {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, - {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, - {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, -] -typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, -] -yarl = [ - {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, - {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, - {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, - {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, - {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, - {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, - {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, - {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, - {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, - {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, - {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, - {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, - {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, -] diff --git a/get_started_documentation/images/skills_architecture.png b/get_started_documentation/images/skills_architecture.png deleted file mode 100644 index 02a76eacc4a34d788ce1fd95cc0b7d6c0f6218ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23420 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.6" [package.dependencies] @@ -18,39 +18,19 @@ yarl = ">=1.0,<2.0" speedups = ["aiodns", "brotlipy", "cchardet"] [[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "arrow" -version = "1.1.1" -description = "Better dates & times for Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -python-dateutil = ">=2.7.0" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "aspy.yaml" -version = "1.3.0" -description = "A few extensions to pyyaml." +name = "asgiref" +version = "3.4.1" +description = "ASGI specs, helper code, and adapters" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" -[package.dependencies] -pyyaml = "*" +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] [[package]] name = "astroid" -version = "2.8.0" +version = "2.8.3" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -58,16 +38,15 @@ python-versions = "~=3.6" [package.dependencies] lazy-object-proxy = ">=1.4.0" -typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<1.13" +wrapt = ">=1.11,<1.14" [[package]] name = "async-timeout" version = "3.0.1" description = "Timeout context manager for asyncio programs" category = "main" -optional = false +optional = true python-versions = ">=3.5.3" [[package]] @@ -78,17 +57,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "atpublic" -version = "2.3" -description = "public -- @public for populating __all__" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing_extensions = {version = "*", markers = "python_version < \"3.8\""} - [[package]] name = "attrs" version = "21.2.0" @@ -123,79 +91,59 @@ category = "dev" optional = false python-versions = ">=2.7" -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] -[[package]] -name = "binaryornot" -version = "0.4.4" -description = "Ultra-lightweight pure Python package to check if a file is binary or text." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -chardet = ">=3.0.2" - [[package]] name = "black" -version = "20.8b1" +version = "21.9b0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" click = ">=7.1.2" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.2)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "blis" -version = "0.7.4" +version = "0.7.5" description = "The Blis BLAS-like linear algebra library, as a self-contained C-extension." category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] numpy = ">=1.15.0" -[[package]] -name = "cached-property" -version = "1.5.2" -description = "A decorator for caching properties in classes." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "catalogue" version = "1.0.0" description = "Super lightweight function registries for your library" category = "main" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -[package.dependencies] -importlib-metadata = {version = ">=0.20", markers = "python_version < \"3.8\""} - [[package]] name = "certifi" -version = "2021.5.30" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -203,7 +151,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.6" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -212,25 +160,17 @@ python-versions = "*" [package.dependencies] pycparser = "*" -[[package]] -name = "cfgv" -version = "3.3.1" -description = "Validate configuration and produce human readable error messages." -category = "dev" -optional = false -python-versions = ">=3.6.1" - [[package]] name = "chardet" version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "charset-normalizer" -version = "2.0.6" +version = "2.0.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -252,7 +192,7 @@ name = "click-log" version = "0.1.8" description = "Logging integration for Click" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -266,117 +206,47 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -category = "main" -optional = false -python-versions = "*" - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - -[[package]] -name = "configobj" -version = "5.0.6" -description = "Config file reading, writing and validation." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[[package]] -name = "cookiecutter" -version = "1.7.3" -description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -binaryornot = ">=0.4.4" -click = ">=7.0" -Jinja2 = ">=2.7,<4.0.0" -jinja2-time = ">=0.2.0" -poyo = ">=0.5.0" -python-slugify = ">=4.0.0" -requests = ">=2.23.0" -six = ">=1.10" - [[package]] name = "coverage" -version = "5.5" +version = "6.0.2" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.6" [package.extras] -toml = ["toml"] +toml = ["tomli"] [[package]] name = "cryptography" -version = "3.3.2" +version = "35.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +python-versions = ">=3.6" [package.dependencies] cffi = ">=1.12" -six = ">=1.4.1" [package.extras] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "cymem" -version = "2.0.5" +version = "2.0.6" description = "Manage calls to calloc/free through Cython" category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "decorator" -version = "5.1.0" -description = "Decorators for Humans" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "dictdiffer" -version = "0.9.0" -description = "Dictdiffer is a library that helps you to diff and patch dictionaries." -category = "main" -optional = false +optional = true python-versions = "*" -[package.extras] -all = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)", "check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "sphinx (>=3)", "tox (>=3.7.0)", "numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "pytest (==5.4.3)", "pytest-pycodestyle (>=2)", "pytest-pydocstyle (>=2)", "pytest (>=6)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2.2.0)", "numpy (>=1.20.0)"] -docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"] -numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"] -tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "sphinx (>=3)", "tox (>=3.7.0)", "pytest (==5.4.3)", "pytest-pycodestyle (>=2)", "pytest-pydocstyle (>=2)", "pytest (>=6)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2.2.0)"] - -[[package]] -name = "diskcache" -version = "5.2.1" -description = "Disk Cache -- Disk and file backed persistent cache." -category = "main" -optional = false -python-versions = ">=3" - [[package]] name = "distlib" -version = "0.3.2" +version = "0.3.3" description = "Distribution utilities" category = "dev" optional = false @@ -387,107 +257,15 @@ name = "distro" version = "1.6.0" description = "Distro - an OS platform information API" category = "main" -optional = false +optional = true python-versions = "*" -[[package]] -name = "dpath" -version = "2.0.5" -description = "Filesystem-like pathing and searching for dictionaries" -category = "main" -optional = false -python-versions = ">=3" - -[[package]] -name = "dulwich" -version = "0.20.25" -description = "Python Git Library" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -certifi = "*" -urllib3 = ">=1.24.1" - -[package.extras] -fastimport = ["fastimport"] -https = ["urllib3[secure] (>=1.24.1)"] -pgp = ["gpg"] -watch = ["pyinotify"] - -[[package]] -name = "dvc" -version = "2.5.4" -description = "Git for data scientists - manage your code and data together" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -appdirs = ">=1.4.3" -colorama = ">=0.3.9" -configobj = ">=5.0.6" -dictdiffer = ">=0.8.1" -diskcache = ">=5.2.1" -distro = ">=1.3.0" -dpath = ">=2.0.1,<3" -dulwich = ">=0.20.23" -flatten-dict = ">=0.3.0,<1" -"flufl.lock" = ">=3.2,<4" -fsspec = ">=2021.6.1" -funcy = ">=1.14" -gitpython = ">3" -grandalf = "0.6" -importlib-metadata = {version = ">=1.4", markers = "python_version < \"3.8\""} -jsonpath-ng = ">=1.5.1" -nanotime = ">=0.5.2" -networkx = ">=2.5,<3.0" -packaging = ">=19.0" -pathspec = ">=0.6.0" -ply = ">=3.9" -psutil = ">=5.8.0" -pyasn1 = ">=0.4.1" -pydot = ">=1.2.4" -pygit2 = ">=1.5.0" -pygtrie = ">=2.3.2" -pyparsing = "2.4.7" -python-benedict = ">=0.21.1" -pywin32 = {version = ">=225", markers = "sys_platform == \"win32\""} -requests = ">=2.22.0" -rich = ">=10.0.0" -"ruamel.yaml" = ">=0.16.1" -shortuuid = ">=0.5.0" -shtab = ">=1.3.4,<2" -speedcopy = {version = ">=2.0.1", markers = "python_version < \"3.8\" and sys_platform == \"win32\""} -tabulate = ">=0.8.7" -toml = ">=0.10.1" -tqdm = ">=4.45.0,<5" -typing-extensions = ">=3.7.4" -voluptuous = ">=0.11.7" -win-unicode-console = {version = ">=0.5", markers = "sys_platform == \"win32\""} -"zc.lockfile" = ">=1.2.1" - -[package.extras] -all = ["gcsfs (==2021.6.1)", "s3fs (==2021.6.1)", "aiobotocore[boto3] (==1.3.0)", "adlfs (==0.7.1)", "azure-identity (>=1.4.0)", "knack", "paramiko[invoke] (>=2.7.0)", "oss2 (==2.6.1)", "pycryptodome (>=3.10)", "pydrive2 (>=1.8.1)", "six (>=1.13.0)", "pyarrow (>=2.0.0)", "hdfs (==2.5.8)", "webdav4 (>=0.8.1)"] -azure = ["adlfs (==0.7.1)", "azure-identity (>=1.4.0)", "knack"] -gdrive = ["pydrive2 (>=1.8.1)", "six (>=1.13.0)"] -gs = ["gcsfs (==2021.6.1)"] -hdfs = ["pyarrow (>=2.0.0)"] -oss = ["oss2 (==2.6.1)", "pycryptodome (>=3.10)"] -s3 = ["s3fs (==2021.6.1)", "aiobotocore[boto3] (==1.3.0)"] -ssh = ["paramiko[invoke] (>=2.7.0)"] -ssh_gssapi = ["paramiko[gssapi,invoke] (>=2.7.0)"] -tests = ["wheel (==0.36.2)", "pytest (==6.2.4)", "pytest-cov (==2.12.1)", "pytest-xdist (==2.3.0)", "pytest-mock (==3.6.1)", "pytest-lazy-fixture (==0.6.3)", "pytest-timeout (==1.4.2)", "pytest-docker (==0.10.3)", "flaky (==3.7.0)", "mock (==4.0.3)", "rangehttpserver (==1.2.0)", "mock-ssh-server (==0.9.0)", "beautifulsoup4 (==4.9.3)", "wget (==3.2)", "filelock (==3.0.12)", "wsgidav (==3.1.0)", "crc32c (==2.2.post0)", "xmltodict (==0.12.0)", "google-compute-engine (==2.8.13)", "google-cloud-storage (==1.40.0)", "urllib3 (==1.26.6)", "Pygments (==2.9.0)", "collective.checkdocs (==0.2)", "pydocstyle (==6.1.1)", "jaraco.windows (==5.5.0)", "pylint (==2.8.3)", "pylint-pytest (==1.0.3)", "pylint-plugin-utils (==0.6)", "mypy (==0.910)", "types-requests", "types-paramiko", "types-tabulate", "types-toml"] -webdav = ["webdav4 (>=0.8.1)"] -webhdfs = ["hdfs (==2.5.8)"] - [[package]] name = "elasticsearch" -version = "7.14.1" +version = "7.13.4" description = "Python client for Elasticsearch" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" [package.dependencies] @@ -501,40 +279,48 @@ docs = ["sphinx (<1.7)", "sphinx-rtd-theme"] requests = ["requests (>=2.4.0,<3.0.0)"] [[package]] -name = "filelock" -version = "3.0.12" -description = "A platform independent file lock." -category = "dev" +name = "fastapi" +version = "0.68.2" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6.1" + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.14.2" + +[package.extras] +all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.8.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] +dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)", "graphene (>=2.1.8,<3.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] +test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "pytest-asyncio (>=0.14.0,<0.16.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.8.0)", "flask (>=1.1.2,<2.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] [[package]] -name = "flake8" -version = "3.9.2" -description = "the modular source code checker: pep8 pyflakes and co" +name = "filelock" +version = "3.3.1" +description = "A platform independent file lock." category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] name = "flask" -version = "1.1.2" +version = "1.1.4" description = "A simple framework for building complex web applications." category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -click = ">=5.1" -itsdangerous = ">=0.24" -Jinja2 = ">=2.10.1" -Werkzeug = ">=0.15" +click = ">=5.1,<8.0" +itsdangerous = ">=0.24,<2.0" +Jinja2 = ">=2.10.1,<3.0" +Werkzeug = ">=0.15,<2.0" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] @@ -546,143 +332,32 @@ name = "flask-cors" version = "3.0.10" description = "A Flask extension adding a decorator for CORS support" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] Flask = ">=0.9" Six = "*" -[[package]] -name = "flatten-dict" -version = "0.4.2" -description = "A flexible utility for flattening and unflattening dict-like objects in Python." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -six = ">=1.12,<2.0" - -[[package]] -name = "flufl.lock" -version = "3.2" -description = "NFS-safe file locking with timeouts for POSIX systems." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -atpublic = "*" - -[[package]] -name = "fsspec" -version = "2021.8.1" -description = "File-system specification" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -dask = ["dask", "distributed"] -dropbox = ["dropboxdrivefs", "requests", "dropbox"] -entrypoints = ["importlib-metadata"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -hdfs = ["pyarrow (>=1)"] -http = ["requests", "aiohttp"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] - -[[package]] -name = "ftfy" -version = "6.0.3" -description = "Fixes some problems with Unicode text after the fact" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -wcwidth = "*" - -[package.extras] -docs = ["furo", "sphinx"] - -[[package]] -name = "funcy" -version = "1.16" -description = "A fancy and practical functional tools" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "future" version = "0.18.2" description = "Clean single-source support for Python 3 and 2" category = "main" -optional = false +optional = true python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -name = "gitdb" -version = "4.0.7" -description = "Git Object Database" -category = "main" -optional = false -python-versions = ">=3.4" - -[package.dependencies] -smmap = ">=3.0.1,<5" - -[[package]] -name = "gitpython" -version = "3.1.24" -description = "GitPython is a python library used to interact with Git repositories" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} - -[[package]] -name = "grandalf" -version = "0.6" -description = "Graph and drawing algorithms framework" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -future = "*" -pyparsing = "*" - -[package.extras] -full = ["numpy", "ply"] - -[[package]] -name = "identify" -version = "2.2.15" -description = "File identification library for Python" +name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" category = "dev" optional = false -python-versions = ">=3.6.1" - -[package.extras] -license = ["editdistance-s"] +python-versions = ">=3.6" [[package]] name = "idna" -version = "3.2" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -693,32 +368,12 @@ name = "immutables" version = "0.16" description = "Immutable Collections" category = "main" -optional = false +optional = true python-versions = ">=3.6" -[package.dependencies] -typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} - [package.extras] test = ["flake8 (>=3.8.4,<3.9.0)", "pycodestyle (>=2.6.0,<2.7.0)", "mypy (>=0.910)", "pytest (>=6.2.4,<6.3.0)"] -[[package]] -name = "importlib-metadata" -version = "4.8.1" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] - [[package]] name = "isort" version = "4.3.21" @@ -735,59 +390,34 @@ xdg_home = ["appdirs (>=1.4.0)"] [[package]] name = "itsdangerous" -version = "2.0.1" -description = "Safely pass data to untrusted environments and back." +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." category = "main" -optional = false -python-versions = ">=3.6" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "jinja2" -version = "3.0.1" +version = "2.11.3" description = "A very fast and expressive template engine." category = "main" -optional = false -python-versions = ">=3.6" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -MarkupSafe = ">=2.0" +MarkupSafe = ">=0.23" [package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jinja2-time" -version = "0.2.0" -description = "Jinja2 Extension for Dates and Times" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -arrow = "*" -jinja2 = "*" +i18n = ["Babel (>=0.8)"] [[package]] name = "joblib" -version = "1.0.1" +version = "1.1.0" description = "Lightweight pipelining with Python functions" category = "main" -optional = false +optional = true python-versions = ">=3.6" -[[package]] -name = "jsonpath-ng" -version = "1.5.3" -description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -decorator = "*" -ply = "*" -six = "*" - [[package]] name = "lazy-object-proxy" version = "1.6.0" @@ -796,20 +426,12 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -[[package]] -name = "mailchecker" -version = "4.0.11" -description = "Cross-language email validation. Backed by a database of thousands throwable email providers." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "markupsafe" version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" -optional = false +optional = true python-versions = ">=3.6" [[package]] @@ -817,7 +439,7 @@ name = "marshmallow" version = "3.7.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.extras] @@ -836,10 +458,10 @@ python-versions = "*" [[package]] name = "mindmeld" -version = "4.3.5rc11" +version = "4.4.0" description = "A Conversational AI platform." category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -848,8 +470,7 @@ attrs = ">=18.2" Click = ">=7.1,<8.0" click-log = "0.1.8" distro = ">=1.3,<2.0" -dvc = ">=1.8.1" -elasticsearch = ">=5.0" +elasticsearch = ">=5.0,<7.14" Flask = ">=1.0,<2.0" Flask-Cors = ">=3.0,<4.0" future = ">=0.17,<1.0" @@ -874,13 +495,27 @@ tqdm = ">=4.15,<5.0" [package.extras] active_learning = ["matplotlib (>=3.3.1,<3.4.0)"] augment = ["sentencepiece (==0.1.91)", "torch (>=1.7.0,<1.8.0)", "transformers (>=3.5.1,<3.6.0)"] -bert = ["elasticsearch (>=7.0)", "torch (>=1.7.0,<1.8.0)", "transformers (>=3.5.1,<3.6.0)", "sentence-transformers (>=0.3,<1.0)"] +bert = ["elasticsearch (>=7.0,<7.14)", "torch (>=1.7.0,<1.8.0)", "transformers (>=3.5.1,<3.6.0)", "sentence-transformers (>=0.3,<1.0)"] bot = ["ciscosparkapi", "twilio"] -elasticsearch = ["elasticsearch (>=5.0)"] +dvc = ["dvc (>=1.8.1)", "pygit2 (>=1.5.0,<1.7)"] +elasticsearch = ["elasticsearch (>=5.0,<7.14)"] examples = ["connexion (>=2.7.0)"] language_annotator = ["google-cloud-translate (>=3.0.1)"] tensorflow = ["tensorflow (>=1.2,<2.0)", "tensorflow (>=1.13.1,<2.0)"] +[[package]] +name = "mock" +version = "4.0.3" +description = "Rolling backport of unittest.mock for all Pythons" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +build = ["twine", "wheel", "blurb"] +docs = ["sphinx"] +test = ["pytest (<5.4)", "pytest-cov"] + [[package]] name = "more-itertools" version = "8.10.0" @@ -891,18 +526,18 @@ python-versions = ">=3.5" [[package]] name = "multidict" -version = "5.1.0" +version = "5.2.0" description = "multidict implementation" category = "main" -optional = false +optional = true python-versions = ">=3.6" [[package]] name = "murmurhash" -version = "1.0.5" +version = "1.0.6" description = "Cython bindings for MurmurHash" category = "main" -optional = false +optional = true python-versions = "*" [[package]] @@ -910,13 +545,12 @@ name = "mypy" version = "0.910" description = "Optional static typing for Python" category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" toml = "*" -typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} typing-extensions = ">=3.7.4" [package.extras] @@ -931,72 +565,41 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "nanotime" -version = "0.5.2" -description = "nanotime python implementation" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "networkx" -version = "2.6.3" -description = "Python package for creating and manipulating graphs and networks" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -default = ["numpy (>=1.19)", "scipy (>=1.5,!=1.6.1)", "matplotlib (>=3.3)", "pandas (>=1.1)"] -developer = ["black (==21.5b1)", "pre-commit (>=2.12)"] -doc = ["sphinx (>=4.0,<5.0)", "pydata-sphinx-theme (>=0.6,<1.0)", "sphinx-gallery (>=0.9,<1.0)", "numpydoc (>=1.1)", "pillow (>=8.2)", "nb2plots (>=0.6)", "texext (>=0.6.6)"] -extra = ["lxml (>=4.5)", "pygraphviz (>=1.7)", "pydot (>=1.4.1)"] -test = ["pytest (>=6.2)", "pytest-cov (>=2.12)", "codecov (>=2.1)"] - [[package]] name = "nltk" -version = "3.6.3" +version = "3.6.5" description = "Natural Language Toolkit" category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] click = "*" joblib = "*" -regex = "*" +regex = ">=2021.8.3" tqdm = "*" [package.extras] -all = ["gensim (<4.0.0)", "numpy", "pyparsing", "scikit-learn", "scipy", "twython", "python-crfsuite", "requests", "matplotlib"] +all = ["numpy", "python-crfsuite", "matplotlib", "twython", "requests", "scikit-learn", "gensim (<4.0.0)", "pyparsing", "scipy"] corenlp = ["requests"] machine_learning = ["gensim (<4.0.0)", "numpy", "python-crfsuite", "scikit-learn", "scipy"] plot = ["matplotlib"] tgrep = ["pyparsing"] twitter = ["twython"] -[[package]] -name = "nodeenv" -version = "1.6.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "numpy" version = "1.21.1" description = "NumPy is the fundamental package for array computing with Python." category = "main" -optional = false +optional = true python-versions = ">=3.7" [[package]] name = "packaging" version = "21.0" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -1007,29 +610,21 @@ pyparsing = ">=2.0.2" name = "pathspec" version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." -category = "main" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -[[package]] -name = "phonenumbers" -version = "8.12.32" -description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "plac" version = "1.1.3" description = "The smartest command line arguments parser in the world" category = "main" -optional = false +optional = true python-versions = "*" [[package]] name = "platformdirs" -version = "2.3.0" +version = "2.4.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -1045,72 +640,23 @@ version = "0.13.1" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] - -[[package]] -name = "ply" -version = "3.11" -description = "Python Lex & Yacc" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "poyo" -version = "0.5.0" -description = "A lightweight YAML Parser for Python. 🐓" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pre-commit" -version = "1.21.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[package.dependencies] -"aspy.yaml" = "*" -cfgv = ">=2.0.0" -identify = ">=1.0.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -nodeenv = ">=0.11.1" -pyyaml = "*" -six = "*" -toml = "*" -virtualenv = ">=15.2" +[package.extras] +dev = ["pre-commit", "tox"] [[package]] name = "preshed" -version = "3.0.5" +version = "3.0.6" description = "Cython hash table that trusts the keys are pre-hashed" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] cymem = ">=2.0.2,<2.1.0" murmurhash = ">=0.28.0,<1.1.0" -[[package]] -name = "psutil" -version = "5.8.0" -description = "Cross-platform lib for process and system monitoring in Python." -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] - [[package]] name = "py" version = "1.10.0" @@ -1119,28 +665,20 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pyasn1" -version = "0.4.8" -description = "ASN.1 types and codecs" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.8.0" description = "Python style guide checker" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycountry" version = "20.7.3" description = "ISO country, subdivision, language, currency and script definitions and their translations" category = "main" -optional = false +optional = true python-versions = "*" [[package]] @@ -1152,51 +690,20 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "pydot" -version = "1.4.2" -description = "Python interface to Graphviz's Dot" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -pyparsing = ">=2.1.4" - -[[package]] -name = "pyflakes" -version = "2.3.1" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pygit2" -version = "1.6.1" -description = "Python bindings for libgit2." +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6.1" [package.dependencies] -cached-property = "*" -cffi = ">=1.4.0" - -[[package]] -name = "pygments" -version = "2.10.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.5" +python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""} +typing-extensions = ">=3.7.4.3" -[[package]] -name = "pygtrie" -version = "2.4.2" -description = "A pure Python trie data structure implementation." -category = "main" -optional = false -python-versions = "*" +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] [[package]] name = "pylint" @@ -1219,7 +726,7 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -1235,7 +742,6 @@ python-versions = ">=3.5" atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=17.4.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" @@ -1246,6 +752,20 @@ wcwidth = "*" checkqa-mypy = ["mypy (==v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.15.1" +description = "Pytest support for asyncio." +category = "dev" +optional = false +python-versions = ">= 3.6" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +testing = ["coverage", "hypothesis (>=5.7.1)"] + [[package]] name = "pytest-cov" version = "2.12.1" @@ -1262,33 +782,12 @@ toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] -[[package]] -name = "python-benedict" -version = "0.24.2" -description = "python-benedict is a dict subclass with keylist/keypath support, normalized I/O operations (base64, csv, ini, json, pickle, plist, query-string, toml, xml, yaml) and many utilities... for humans, obviously." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -ftfy = {version = "*", markers = "python_version >= \"3.6\""} -mailchecker = "*" -phonenumbers = "*" -python-dateutil = "*" -python-fsutil = "*" -python-slugify = "*" -pyyaml = "*" -requests = "*" -six = "*" -toml = "*" -xmltodict = "*" - [[package]] name = "python-crfsuite" version = "0.9.7" description = "Python binding for CRFsuite" category = "main" -optional = false +optional = true python-versions = "*" [[package]] @@ -1296,64 +795,42 @@ name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" category = "main" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" [[package]] -name = "python-fsutil" -version = "0.5.0" -description = "file-system utilities for lazy devs." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -requests = "*" - -[[package]] -name = "python-slugify" -version = "5.0.2" -description = "A Python Slugify application that handles Unicode" +name = "python-dotenv" +version = "0.19.1" +description = "Read key-value pairs from a .env file and set them as environment variables" category = "main" optional = false -python-versions = ">=3.6" - -[package.dependencies] -text-unidecode = ">=1.3" +python-versions = ">=3.5" [package.extras] -unidecode = ["Unidecode (>=1.1.1)"] +cli = ["click (>=5.0)"] [[package]] name = "pytz" -version = "2021.1" +version = "2021.3" description = "World timezone definitions, modern and historical" category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pywin32" -version = "301" -description = "Python for Window Extensions" -category = "main" -optional = false +optional = true python-versions = "*" [[package]] name = "pyyaml" -version = "5.4.1" +version = "6.0" description = "YAML parser and emitter for Python" category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +optional = true +python-versions = ">=3.6" [[package]] name = "regex" -version = "2021.8.28" +version = "2021.10.21" description = "Alternative regular expression module, to replace re." category = "main" optional = false @@ -1377,52 +854,12 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] -[[package]] -name = "rich" -version = "10.10.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.6,<4.0" - -[package.dependencies] -colorama = ">=0.4.0,<0.5.0" -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" -typing-extensions = {version = ">=3.7.4,<4.0.0", markers = "python_version < \"3.8\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] - -[[package]] -name = "ruamel.yaml" -version = "0.17.16" -description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -category = "main" -optional = false -python-versions = ">=3" - -[package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.10\""} - -[package.extras] -docs = ["ryd"] -jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] - -[[package]] -name = "ruamel.yaml.clib" -version = "0.2.6" -description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -category = "main" -optional = false -python-versions = ">=3.5" - [[package]] name = "scikit-learn" version = "0.19.2" description = "A set of python modules for machine learning and data mining" category = "main" -optional = false +optional = true python-versions = "*" [package.extras] @@ -1433,28 +870,12 @@ name = "scipy" version = "1.6.1" description = "SciPy: Scientific Library for Python" category = "main" -optional = false +optional = true python-versions = ">=3.7" [package.dependencies] numpy = ">=1.16.5" -[[package]] -name = "shortuuid" -version = "1.0.1" -description = "A generator library for concise, unambiguous and URL-safe UUIDs." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "shtab" -version = "1.4.1" -description = "Automatically generate shell tab completion scripts for python CLI apps" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,>=2.7" - [[package]] name = "six" version = "1.16.0" @@ -1468,7 +889,7 @@ name = "sklearn-crfsuite" version = "0.3.6" description = "CRFsuite (python-crfsuite) wrapper which provides interface simlar to scikit-learn" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -1477,20 +898,12 @@ six = "*" tabulate = "*" tqdm = ">=2.0" -[[package]] -name = "smmap" -version = "4.0.0" -description = "A pure Python implementation of a sliding window memory map manager" -category = "main" -optional = false -python-versions = ">=3.5" - [[package]] name = "spacy" version = "2.3.7" description = "Industrial-strength Natural Language Processing (NLP) in Python" category = "main" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] @@ -1524,46 +937,41 @@ lookups = ["spacy-lookups-data (>=0.3.2,<0.4.0)"] th = ["pythainlp (>=2.0)"] [[package]] -name = "speedcopy" -version = "2.1.0" -description = "Replacement or alternative for python copyfile()utilizing server side copy on network shares for fastercopying." +name = "srsly" +version = "1.0.5" +description = "Modern high-performance serialization utilities for Python" category = "main" -optional = false +optional = true python-versions = "*" [[package]] -name = "srsly" -version = "1.0.5" -description = "Modern high-performance serialization utilities for Python" +name = "starlette" +version = "0.14.2" +description = "The little ASGI library that shines." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" + +[package.extras] +full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] [[package]] name = "tabulate" version = "0.8.9" description = "Pretty-print tabular data" category = "main" -optional = false +optional = true python-versions = "*" [package.extras] widechars = ["wcwidth"] -[[package]] -name = "text-unidecode" -version = "1.3" -description = "The most basic Text::Unidecode port" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "thinc" version = "7.4.5" description = "Practical Machine Learning for NLP" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -1598,6 +1006,14 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "1.2.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "tox" version = "3.24.4" @@ -1609,7 +1025,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" @@ -1626,7 +1041,7 @@ name = "tqdm" version = "4.62.3" description = "Fast, Extensible Progress Meter" category = "main" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.dependencies] @@ -1637,14 +1052,6 @@ dev = ["py-make (>=0.1.0)", "twine", "wheel"] notebook = ["ipywidgets (>=6)"] telegram = ["requests"] -[[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "typer" version = "0.4.0" @@ -1672,7 +1079,7 @@ python-versions = "*" [[package]] name = "urllib3" -version = "1.26.6" +version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -1683,9 +1090,25 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "uvicorn" +version = "0.15.0" +description = "The lightning-fast ASGI server." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +asgiref = ">=3.4.0" +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["websockets (>=9.1)", "httptools (>=0.2.0,<0.3.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] + [[package]] name = "virtualenv" -version = "20.8.0" +version = "20.8.1" description = "Virtual Python Environment builder" category = "dev" optional = false @@ -1695,7 +1118,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" "backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" @@ -1703,105 +1125,61 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] -[[package]] -name = "voluptuous" -version = "0.12.1" -description = "" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "wasabi" version = "0.8.2" description = "A lightweight console printing and formatting toolkit" category = "main" -optional = false +optional = true python-versions = "*" [[package]] name = "wcwidth" version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" +category = "dev" optional = false python-versions = "*" [[package]] name = "werkzeug" -version = "2.0.1" +version = "1.0.1" description = "The comprehensive WSGI web application library." category = "main" -optional = false -python-versions = ">=3.6" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] +dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] watchdog = ["watchdog"] -[[package]] -name = "win-unicode-console" -version = "0.5" -description = "Enable Unicode input and display when running Python from Windows console." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "wrapt" -version = "1.12.1" +version = "1.13.2" description = "Module for decorators, wrappers and monkey patching." category = "dev" optional = false -python-versions = "*" - -[[package]] -name = "xmltodict" -version = "0.12.0" -description = "Makes working with XML feel like you are working with JSON" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "yarl" -version = "1.6.3" +version = "1.7.0" description = "Yet another URL library" category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] idna = ">=2.0" multidict = ">=4.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[[package]] -name = "zc.lockfile" -version = "2.0" -description = "Basic inter-process locks" -category = "main" -optional = false -python-versions = "*" -[package.extras] -test = ["zope.testing"] - -[[package]] -name = "zipp" -version = "3.5.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +[extras] +mindmeld = ["mindmeld"] [metadata] lock-version = "1.1" -python-versions = "^3.7" -content-hash = "413fbc70227930179db755430dede7da195c5ab9ae9d82fbdc914208f95e1627" +python-versions = "^3.8" +content-hash = "9e88f5c779474003b379486aed11c6aa2d854fa2e55b30b77ba28518887cd2ec" [metadata.files] aiohttp = [ @@ -1843,21 +1221,13 @@ aiohttp = [ {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, ] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -arrow = [ - {file = "arrow-1.1.1-py3-none-any.whl", hash = "sha256:77a60a4db5766d900a2085ce9074c5c7b8e2c99afeaa98ad627637ff6f292510"}, - {file = "arrow-1.1.1.tar.gz", hash = "sha256:dee7602f6c60e3ec510095b5e301441bc56288cb8f51def14dcb3079f623823a"}, -] -"aspy.yaml" = [ - {file = "aspy.yaml-1.3.0-py2.py3-none-any.whl", hash = "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"}, - {file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"}, +asgiref = [ + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, ] astroid = [ - {file = "astroid-2.8.0-py3-none-any.whl", hash = "sha256:dcc06f6165f415220013801642bd6c9808a02967070919c4b746c6864c205471"}, - {file = "astroid-2.8.0.tar.gz", hash = "sha256:fe81f80c0b35264acb5653302ffbd935d394f1775c5e4487df745bf9c2442708"}, + {file = "astroid-2.8.3-py3-none-any.whl", hash = "sha256:f9d66e3a4a0e5b52819b2ff41ac2b179df9d180697db71c92beb33a60c661794"}, + {file = "astroid-2.8.3.tar.gz", hash = "sha256:0e361da0744d5011d4f5d57e64473ba9b7ab4da1e2d45d6631ebd67dd28c3cce"}, ] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, @@ -1867,9 +1237,6 @@ atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] -atpublic = [ - {file = "atpublic-2.3.tar.gz", hash = "sha256:d6b9167fc3e09a2de2d2adcfc9a1b48d84eab70753c97de3800362e1703e3367"}, -] attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, @@ -1882,98 +1249,95 @@ autopep8 = [ {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, ] -binaryornot = [ - {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, - {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, -] black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, + {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, + {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, ] blis = [ - {file = "blis-0.7.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5b403deb2ad5515e1edb3c0867bccb5b974b461f24283d9219a3a761fd6dacc6"}, - {file = "blis-0.7.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9f9b829480c12fc834549306821e5c51cb28b216ca5f88c5b2cfedbeb9daf67d"}, - {file = "blis-0.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c2d8064217c326dd9a0dcbae294ffe8557263e2a00d76101ffa222b9c9d9c62d"}, - {file = "blis-0.7.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d717b5dea407aac89a646908e7d9849105abab9c88a539c120518c200f899f4e"}, - {file = "blis-0.7.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5ecddc4c6daf80558154b091db0a9839bb15dbe65d2906a543a73b93fbce4f73"}, - {file = "blis-0.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6814991b3e3193db4f9b2417174c6f24b9c0197409d864fa7628583bd2df1f0f"}, - {file = "blis-0.7.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4222bbc7b9c47bc3cf6f36f2241862c1512ca7ebac3828267a2e05ef6c47fc54"}, - {file = "blis-0.7.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:445e4838b809e99677f5c0982fb9af320f0d91328fb28c8097e5f1173c4df9d6"}, - {file = "blis-0.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:94890b2296f1449baa56aede46627ea7fc8de11c788f9c261ee38c2eb4a2cc7d"}, - {file = "blis-0.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:168fd7bd763ebe529aa25a066d3a6b89f4c3f492f6297f881df6942741b95787"}, - {file = "blis-0.7.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:5c1a2023f7d8431daa8d87d32f539bb23e1a009500c37f9eba0ac7b3f20f73eb"}, - {file = "blis-0.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:78a8e0ee72a42c3b2f5b9114500a781119995f76fa6c21d4b02c6fb9c21df2cc"}, - {file = "blis-0.7.4.tar.gz", hash = "sha256:7daa615a97d4f28db0f332b710bfe1900b15d0c25841c6d727965e4fd91e09cf"}, -] -cached-property = [ - {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, - {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, + {file = "blis-0.7.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:98eba77b1e1fde7813bc0453ab78b6ae2067f5bc0fe9e3abc671b2895cfecf33"}, + {file = "blis-0.7.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eecfce3d8fce61dede7b0ae0dffa461c22072437b6cde85587db0c1aa75b450"}, + {file = "blis-0.7.5-cp310-cp310-win_amd64.whl", hash = "sha256:0e476931f0d5703a21c77e7f69b8ebdeeea493fc7858a86f627ac2b376a12c8d"}, + {file = "blis-0.7.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5966ddf3bce84aa7bb09ce4ca059309602fa63280a5d5e5365bb2a294bd5a138"}, + {file = "blis-0.7.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9034dabce4e42e3a1a7b99cc6de430484c8c369e51556ee8d47a53c085de681"}, + {file = "blis-0.7.5-cp36-cp36m-win_amd64.whl", hash = "sha256:730952f74adb0fa7dde9f1bc11249d5a64f3a3a9cf7dfa23b189a4b767bdf2d0"}, + {file = "blis-0.7.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2339cb19594134775bda8b86f23a893828fc7e8d63f09ba9a15f30b2b16c966c"}, + {file = "blis-0.7.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5023781272e0b2868be2f92017aa6836557990f1ca5ba2af5e9f5a0acf04fd8a"}, + {file = "blis-0.7.5-cp37-cp37m-win_amd64.whl", hash = "sha256:65ba723821cc57eb4227eb8dd05c57fff23d97f826d4325b316cd8a63aac8d6a"}, + {file = "blis-0.7.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad4af690c37a5953d3aea660ad89b636bfbb80ca1470995554670ca2143f0cb2"}, + {file = "blis-0.7.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf11c233ea5c2d30683e7c9641c5dc4cd76ed0f64755ba3321dfb8db39feb316"}, + {file = "blis-0.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:31401da283ed42905f0fbf2f8b88ea424c6a911482426f84b5b88c54d382e4d1"}, + {file = "blis-0.7.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c185979f8f528d634f5548b8cd84ab0366d340c27c039ad3937fab186c1c252"}, + {file = "blis-0.7.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8345bd04777557ef385e2f2d1f14a19d53b2ea9ca5fe107a2cdc50d7bafb8eb2"}, + {file = "blis-0.7.5-cp39-cp39-win_amd64.whl", hash = "sha256:66204a19e38986645940c887498c7b5520efb5bbc6526bf1b8a58f7d3eb37da0"}, + {file = "blis-0.7.5.tar.gz", hash = "sha256:833e01e9eaff4c01aa6e049bbc1e6acb9eca6ee513d7b35b5bf135d49705ad33"}, ] catalogue = [ {file = "catalogue-1.0.0-py2.py3-none-any.whl", hash = "sha256:584d78e7f4c3c6e2fd498eb56dfc8ef1f4ff738480237de2ccd26cbe2cf47172"}, {file = "catalogue-1.0.0.tar.gz", hash = "sha256:d74d1d856c6b36a37bf14aa6dbbc27d0582667b7ab979a6108e61a575e8723f5"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ - {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, - {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, - {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, - {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, - {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, - {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, - {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, - {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, - {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, - {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, - {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, - {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, - {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, - {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, - {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, - {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, - {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, - {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, - {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, - {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, + {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, + {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1987,212 +1351,119 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] -commonmark = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] -configobj = [ - {file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"}, -] -cookiecutter = [ - {file = "cookiecutter-1.7.3-py2.py3-none-any.whl", hash = "sha256:f8671531fa96ab14339d0c59b4f662a4f12a2ecacd94a0f70a3500843da588e2"}, - {file = "cookiecutter-1.7.3.tar.gz", hash = "sha256:6b9a4d72882e243be077a7397d0f1f76fe66cf3df91f3115dbb5330e214fa457"}, -] coverage = [ - {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, + {file = "coverage-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd"}, + {file = "coverage-6.0.2-cp310-cp310-win32.whl", hash = "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7"}, + {file = "coverage-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d"}, + {file = "coverage-6.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2"}, + {file = "coverage-6.0.2-cp36-cp36m-win32.whl", hash = "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122"}, + {file = "coverage-6.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9"}, + {file = "coverage-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1"}, + {file = "coverage-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330"}, + {file = "coverage-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1"}, + {file = "coverage-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb"}, + {file = "coverage-6.0.2-cp38-cp38-win32.whl", hash = "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f"}, + {file = "coverage-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9"}, + {file = "coverage-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe"}, + {file = "coverage-6.0.2-cp39-cp39-win32.whl", hash = "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce"}, + {file = "coverage-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9"}, + {file = "coverage-6.0.2-pp36-none-any.whl", hash = "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164"}, + {file = "coverage-6.0.2-pp37-none-any.whl", hash = "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895"}, + {file = "coverage-6.0.2.tar.gz", hash = "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149"}, ] cryptography = [ - {file = "cryptography-3.3.2-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:541dd758ad49b45920dda3b5b48c968f8b2533d8981bcdb43002798d8f7a89ed"}, - {file = "cryptography-3.3.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:49570438e60f19243e7e0d504527dd5fe9b4b967b5a1ff21cc12b57602dd85d3"}, - {file = "cryptography-3.3.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a4ac9648d39ce71c2f63fe7dc6db144b9fa567ddfc48b9fde1b54483d26042"}, - {file = "cryptography-3.3.2-cp27-cp27m-win32.whl", hash = "sha256:aa4969f24d536ae2268c902b2c3d62ab464b5a66bcb247630d208a79a8098e9b"}, - {file = "cryptography-3.3.2-cp27-cp27m-win_amd64.whl", hash = "sha256:1bd0ccb0a1ed775cd7e2144fe46df9dc03eefd722bbcf587b3e0616ea4a81eff"}, - {file = "cryptography-3.3.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e18e6ab84dfb0ab997faf8cca25a86ff15dfea4027b986322026cc99e0a892da"}, - {file = "cryptography-3.3.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c7390f9b2119b2b43160abb34f63277a638504ef8df99f11cb52c1fda66a2e6f"}, - {file = "cryptography-3.3.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:0d7b69674b738068fa6ffade5c962ecd14969690585aaca0a1b1fc9058938a72"}, - {file = "cryptography-3.3.2-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:922f9602d67c15ade470c11d616f2b2364950602e370c76f0c94c94ae672742e"}, - {file = "cryptography-3.3.2-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:a0f0b96c572fc9f25c3f4ddbf4688b9b38c69836713fb255f4a2715d93cbaf44"}, - {file = "cryptography-3.3.2-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:a777c096a49d80f9d2979695b835b0f9c9edab73b59e4ceb51f19724dda887ed"}, - {file = "cryptography-3.3.2-cp36-abi3-win32.whl", hash = "sha256:3c284fc1e504e88e51c428db9c9274f2da9f73fdf5d7e13a36b8ecb039af6e6c"}, - {file = "cryptography-3.3.2-cp36-abi3-win_amd64.whl", hash = "sha256:7951a966613c4211b6612b0352f5bf29989955ee592c4a885d8c7d0f830d0433"}, - {file = "cryptography-3.3.2.tar.gz", hash = "sha256:5a60d3780149e13b7a6ff7ad6526b38846354d11a15e21068e57073e29e19bed"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6"}, + {file = "cryptography-35.0.0-cp36-abi3-win32.whl", hash = "sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8"}, + {file = "cryptography-35.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c"}, + {file = "cryptography-35.0.0.tar.gz", hash = "sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d"}, ] cymem = [ - {file = "cymem-2.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9d72d69f7a62a280199c3aa7bc550685c47d6d0689b2d299e6492253b86d2437"}, - {file = "cymem-2.0.5-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:8ea57e6923f40eb51012352161bb5707c14a5a5ce901ff72021e59df06221655"}, - {file = "cymem-2.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:4bd023c2477198b39b660c2a6b0242880649765ecee8461688a57fd4afd2bfc0"}, - {file = "cymem-2.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f0eb9b3d03623dcfc746cf8bff0663b0e347f4aea759965c8932087a0307ee9"}, - {file = "cymem-2.0.5-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:a440d63577fcdc9c528c9cc026b7b4f8648193bac462bc0596c9eac10f9fba62"}, - {file = "cymem-2.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:3d48902d7441645835fefc7832df49feb5362c7300d182475b63a01d25ae44ef"}, - {file = "cymem-2.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2167c9959fcd639b95d51fa5efaa7c61eef8d686cb75a25412a914f428ce980"}, - {file = "cymem-2.0.5-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:734d82d0d03c2ceb929bc1744c04dbe0a105e68a4947c8406056a36f86c41830"}, - {file = "cymem-2.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:01d3ea159f7a3f3192b1e800ed8207dac7586794d903a153198b9ea317f144bc"}, - {file = "cymem-2.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d307f7f6230d861a938837cae4b855226b6845a21c010242a15e9ce6853856cd"}, - {file = "cymem-2.0.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ce1e81c1d031f56b67bac2136e73b4512cbc794706cd570178972d54ba6115d8"}, - {file = "cymem-2.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d19f68b90411e02ab33b1654118337f96f41c13a3cd00c4f44f7abed2bc712e7"}, - {file = "cymem-2.0.5.tar.gz", hash = "sha256:190e15d9cf2c3bde60ae37bddbae6568a36044dc4a326d84081a5fa08818eee0"}, -] -decorator = [ - {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"}, - {file = "decorator-5.1.0.tar.gz", hash = "sha256:e59913af105b9860aa2c8d3272d9de5a56a4e608db9a2f167a8480b323d529a7"}, -] -dictdiffer = [ - {file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"}, - {file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"}, -] -diskcache = [ - {file = "diskcache-5.2.1-py3-none-any.whl", hash = "sha256:6e8137c778fd2752b93c8a8f944e939b3665d645b46774d8537dd3528ac3baa1"}, - {file = "diskcache-5.2.1.tar.gz", hash = "sha256:1805acd5868ac10ad547208951a1190a0ab7bbff4e70f9a07cde4dbdfaa69f64"}, + {file = "cymem-2.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2b4e27e739f09f16c7c0190f962ffe60dab39cb6a229d5c13e274d16f46a17e8"}, + {file = "cymem-2.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:971cf0a8437dfb4185c3049c086e463612fe849efadc0f5cc153fc81c501da7d"}, + {file = "cymem-2.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:6b0d1a6b0a1296f31fa9e4b7ae5ea49394084ecc883b1ae6fec4844403c43468"}, + {file = "cymem-2.0.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b8e1c18bb00800425576710468299153caad20c64ddb6819d40a6a34e21ee21c"}, + {file = "cymem-2.0.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:492084aef23ac2ff3da3729e9d36340bc91a96c2dc8c3a82a1926e384ab52412"}, + {file = "cymem-2.0.6-cp36-cp36m-win_amd64.whl", hash = "sha256:af3c01e6b20f9e6c07c7d7cdb7f710e49889d3906c9a3e039546ee6636a34b9a"}, + {file = "cymem-2.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d7a59cef8f2fa25d12e2c30138f8623acbd43ad2715e730a709e49c5eef8e1b0"}, + {file = "cymem-2.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd52d8a81881804625df88453611175ab7e0099b34f52204da1f6940cf2e83c9"}, + {file = "cymem-2.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:4749f220e4c06ec44eb10de13794ff0508cdc4f8eff656cf49cab2cdb3122c0c"}, + {file = "cymem-2.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2aa3fa467d906cd2c27fa0a2e2952dd7925f5fcc7973fab6d815ef6acb25aad8"}, + {file = "cymem-2.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea535f74ab6024e7416f93de564e5c81fb7c0964b96280de66f60aeb05f0cf53"}, + {file = "cymem-2.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:4f87fe087f2ae36c3e20e2b1a29d7f76a28c035372d0a97655f26223d975235a"}, + {file = "cymem-2.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a93fba62fe79dbf6fc4d5b6d804a6e114b44af3ff3d40a28833ee39f21bd336b"}, + {file = "cymem-2.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04676d696596b0db3f3c5a3936bab12fb6f24278921a6622bb185e61765b2b4d"}, + {file = "cymem-2.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:c59293b232b53ebb47427f16cf648e937022f489cff36c11d1d8a1f0075b6609"}, + {file = "cymem-2.0.6.tar.gz", hash = "sha256:169725b5816959d34de2545b33fee6a8021a6e08818794a426c5a4f981f17e5e"}, ] distlib = [ - {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, - {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, + {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, + {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, ] distro = [ {file = "distro-1.6.0-py2.py3-none-any.whl", hash = "sha256:c8713330ab31a034623a9515663ed87696700b55f04556b97c39cd261aa70dc7"}, {file = "distro-1.6.0.tar.gz", hash = "sha256:83f5e5a09f9c5f68f60173de572930effbcc0287bb84fdc4426cb4168c088424"}, ] -dpath = [ - {file = "dpath-2.0.5-py3-none-any.whl", hash = "sha256:e7813fd8a9dd0d4c7cd4014533ce955eff712bcb2e8189be79bb893890a9db01"}, - {file = "dpath-2.0.5.tar.gz", hash = "sha256:ef74321b01479653c812fee69c53922364614d266a8e804d22058c5c02e5674e"}, -] -dulwich = [ - {file = "dulwich-0.20.25-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da7655385e090b805c262df42f8b75a115345343951ca2497476df0a6287c20e"}, - {file = "dulwich-0.20.25-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad76fca381ceb914d156eb25c0cc77132cff1156d035271203c521134472b6"}, - {file = "dulwich-0.20.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d4dbdabf71db1028f64e868081746869c17d8dbb6a16abb89b707e7a5590b121"}, - {file = "dulwich-0.20.25-cp36-cp36m-win_amd64.whl", hash = "sha256:12edad6ef398815fff9e51223be5838cd66bcdb164a442f652494ae9bb54720c"}, - {file = "dulwich-0.20.25-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:0d09bc588bfdeca14b3e41d5d75aa4f9ea6b0b298b283bc29c3aa1b2c7788653"}, - {file = "dulwich-0.20.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f676d89db4777e854b28ea87b9bb1b0b156f6a743564c0daeb88502adaec7313"}, - {file = "dulwich-0.20.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eb2cbc0bd62e21011161bde7f4bad7a606d7fbafe4b2e06f09d0cb8fac2e2fd2"}, - {file = "dulwich-0.20.25-cp37-cp37m-win_amd64.whl", hash = "sha256:3be5fc65f84787bd9039aabf2b8b7ad1a02311a16cd0e851504784b62ba91d61"}, - {file = "dulwich-0.20.25-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:6dcd552e8b6899da4cb87e3a0d71673fde03d2d8cf80d2d8946e4ccca15b307c"}, - {file = "dulwich-0.20.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75816b242340c413814d22a8011d0596279ea8f4758bf2f91efd45388915f5d8"}, - {file = "dulwich-0.20.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6d060f996fba4f7aa94e1fedc53e76e8e9ef5b8f7ec026d804a68d63b8df2867"}, - {file = "dulwich-0.20.25-cp38-cp38-win_amd64.whl", hash = "sha256:9b432869199972f0159e7d8cee02fde088c576ff294a01f5253a4ae7cb8f57c4"}, - {file = "dulwich-0.20.25-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:fabad0116db6f14568fef36962382c2ed698b8d305d2baaec9501a76eb836edd"}, - {file = "dulwich-0.20.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e9649b962b753205e0a9a0cf2537871cb89eb3751f6c24e4d5f0d349c96b394"}, - {file = "dulwich-0.20.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:962bb4ce269f8959c818bf58b92fac601ef6c3f63a07ac46a526c83ec5bfb635"}, - {file = "dulwich-0.20.25-cp39-cp39-win_amd64.whl", hash = "sha256:b10d8cf0e71395871c90ca29ef794ecf04665ba41550029842b53c03af1c6ac5"}, - {file = "dulwich-0.20.25.tar.gz", hash = "sha256:79baea81583eb61eb7bd4a819ab6096686b362c626a4640d84d4fc5539139353"}, -] -dvc = [ - {file = "dvc-2.5.4-py3-none-any.whl", hash = "sha256:92f68185eb1b024991a211354b83af32749b361e2cbd5b754023ff414b90eaac"}, - {file = "dvc-2.5.4.tar.gz", hash = "sha256:0b3b5ff97161b37ecf64e1189c9c34d79215b3a0f516026ae5b3d5e10dc29f92"}, -] elasticsearch = [ - {file = "elasticsearch-7.14.1-py2.py3-none-any.whl", hash = "sha256:1a9f146b7126a7e0621085f1825b6b2d091693a714d3b16c208749762e79c2bc"}, - {file = "elasticsearch-7.14.1.tar.gz", hash = "sha256:f928898fe06869516f2603f9a96a6f166c06888233806b31ac6568bac0266501"}, + {file = "elasticsearch-7.13.4-py2.py3-none-any.whl", hash = "sha256:5920df0ab2630778680376d86bea349dc99860977eec9b6d2bd0860f337313f2"}, + {file = "elasticsearch-7.13.4.tar.gz", hash = "sha256:52dda85f76eeb85ec873bf9ffe0ba6849e544e591f66d4048a5e48016de268e0"}, ] -filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +fastapi = [ + {file = "fastapi-0.68.2-py3-none-any.whl", hash = "sha256:36bcdd3dbea87c586061005e4a40b9bd0145afd766655b4e0ec1d8870b32555c"}, + {file = "fastapi-0.68.2.tar.gz", hash = "sha256:38526fc46bda73f7ec92033952677323c16061e70a91d15c95f18b11895da494"}, ] -flake8 = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +filelock = [ + {file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"}, + {file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"}, ] flask = [ - {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, - {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, + {file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"}, + {file = "Flask-1.1.4.tar.gz", hash = "sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196"}, ] flask-cors = [ {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, ] -flatten-dict = [ - {file = "flatten-dict-0.4.2.tar.gz", hash = "sha256:506a96b6e6f805b81ae46a0f9f31290beb5fa79ded9d80dbe1b7fa236ab43076"}, - {file = "flatten_dict-0.4.2-py2.py3-none-any.whl", hash = "sha256:7e245b20c4c718981212210eec4284a330c9f713e632e98765560e05421e48ad"}, -] -"flufl.lock" = [ - {file = "flufl.lock-3.2.tar.gz", hash = "sha256:a8d66accc9ab41f09961cd8f8db39f9c28e97e2769659a3567c63930a869ff5b"}, -] -fsspec = [ - {file = "fsspec-2021.8.1-py3-none-any.whl", hash = "sha256:30f27c059a414d1f434b14b2d0e75c8d9c3dd473ad8daeccb444d9d4069b9f03"}, - {file = "fsspec-2021.8.1.tar.gz", hash = "sha256:af125917788b77782899bbd4484d29e5407f53d2bb04cdfa025fe4931201a555"}, -] -ftfy = [ - {file = "ftfy-6.0.3.tar.gz", hash = "sha256:ba71121a9c8d7790d3e833c6c1021143f3e5c4118293ec3afb5d43ed9ca8e72b"}, -] -funcy = [ - {file = "funcy-1.16-py2.py3-none-any.whl", hash = "sha256:1d3fc5d42cf7564a6b2be04042d0df7a50c77903cf760a34786d0c9ebd659b25"}, - {file = "funcy-1.16.tar.gz", hash = "sha256:2775409b7dc9106283f1224d97e6df5f2c02e7291c8caed72764f5a115dffb50"}, -] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] -gitdb = [ - {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, - {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, -] -gitpython = [ - {file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"}, - {file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"}, -] -grandalf = [ - {file = "grandalf-0.6-py2-none-any.whl", hash = "sha256:d671fdfa5310f2639538b6c858c4b1aef7755535ccfcb9c1cdf0d29171326add"}, - {file = "grandalf-0.6-py2.7.egg", hash = "sha256:51e34e141be2dd19bca74448918ebce88c1cd95498842a13d191df0a52a3955a"}, - {file = "grandalf-0.6-py3-none-any.whl", hash = "sha256:357946e2fd35fc92c327cf3c091acc5aef93e0c74c60fed0a727d827ab3b1272"}, - {file = "grandalf-0.6-py3.5.egg", hash = "sha256:aa985ba9992aba30e9c09d3cd9574054268b74ff2d34ced68aa9e7ca698724b0"}, - {file = "grandalf-0.6.tar.gz", hash = "sha256:7471db231bd7338bc0035b16edf0dc0c900c82d23060f4b4d0c4304caedda6e4"}, -] -identify = [ - {file = "identify-2.2.15-py2.py3-none-any.whl", hash = "sha256:de83a84d774921669774a2000bf87ebba46b4d1c04775f4a5d37deff0cf39f73"}, - {file = "identify-2.2.15.tar.gz", hash = "sha256:528a88021749035d5a39fe2ba67c0642b8341aaf71889da0e1ed669a429b87f0"}, +h11 = [ + {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, + {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, ] idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] immutables = [ {file = "immutables-0.16-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:acbfa79d44228d96296279068441f980dc63dbed52522d9227ff9f4d96c6627e"}, @@ -2223,34 +1494,21 @@ immutables = [ {file = "immutables-0.16-cp39-cp39-win_amd64.whl", hash = "sha256:2505d93395d3f8ae4223e21465994c3bc6952015a38dc4f03cb3e07a2b8d8325"}, {file = "immutables-0.16.tar.gz", hash = "sha256:d67e86859598eed0d926562da33325dac7767b7b1eff84e232c22abea19f4360"}, ] -importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, -] isort = [ {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, ] itsdangerous = [ - {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, - {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, ] jinja2 = [ - {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, - {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, -] -jinja2-time = [ - {file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"}, - {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"}, + {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, + {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] joblib = [ - {file = "joblib-1.0.1-py3-none-any.whl", hash = "sha256:feeb1ec69c4d45129954f1b7034954241eedfd6ba39b5e9e4b6883be3332d5e5"}, - {file = "joblib-1.0.1.tar.gz", hash = "sha256:9c17567692206d2f3fb9ecf5e991084254fe631665c450b443761c4186a613f7"}, -] -jsonpath-ng = [ - {file = "jsonpath-ng-1.5.3.tar.gz", hash = "sha256:a273b182a82c1256daab86a313b937059261b5c5f8c4fa3fc38b882b344dd567"}, - {file = "jsonpath_ng-1.5.3-py2-none-any.whl", hash = "sha256:f75b95dbecb8a0f3b86fd2ead21c2b022c3f5770957492b9b6196ecccfeb10aa"}, - {file = "jsonpath_ng-1.5.3-py3-none-any.whl", hash = "sha256:292a93569d74029ba75ac2dc3d3630fc0e17b2df26119a165fa1d498ca47bf65"}, + {file = "joblib-1.1.0-py2.py3-none-any.whl", hash = "sha256:f21f109b3c7ff9d95f8387f752d0d9c34a02aa2f7060c2135f465da0e5160ff6"}, + {file = "joblib-1.1.0.tar.gz", hash = "sha256:4158fcecd13733f8be669be0683b96ebdbbd38d23559f54dca7205aea1bf1e35"}, ] lazy-object-proxy = [ {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, @@ -2276,9 +1534,6 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, ] -mailchecker = [ - {file = "mailchecker-4.0.11.tar.gz", hash = "sha256:b180600943a17b2cecc5c09b9f25b70658d0f384e6eada06e46ea1d447817f89"}, -] markupsafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, @@ -2344,69 +1599,107 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mindmeld = [ - {file = "mindmeld-4.3.5rc11-py2.py3-none-any.whl", hash = "sha256:105a7da31171b3dcb698769197c32797512e8809cf8e4159936028dbb6ee1d9b"}, + {file = "mindmeld-4.4.0-py2.py3-none-any.whl", hash = "sha256:fe37d7a3dc53ce556a24fd6b08e0d9483f5fdd059b2f0bf28202273a033729db"}, +] +mock = [ + {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, + {file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"}, ] more-itertools = [ {file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"}, {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"}, ] multidict = [ - {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, - {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, - {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, - {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, - {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, - {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, - {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, - {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, - {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, - {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, - {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, - {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, - {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"}, + {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"}, + {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"}, + {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"}, + {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"}, + {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"}, + {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"}, + {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"}, + {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"}, + {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"}, + {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"}, + {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"}, + {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"}, + {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"}, ] murmurhash = [ - {file = "murmurhash-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ef8819d15973e0d6f69688bafc097a1fae081675c1de39807028869a1320b1a9"}, - {file = "murmurhash-1.0.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:76251513a2acad6c2e4b7aeffc5fcb807ee97a66cad5c2990557556555a6b7e9"}, - {file = "murmurhash-1.0.5-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:d58315961dc5a5e740f41f2ac5c3a0ebc61ef472f8afeb4db7eeb3b863243105"}, - {file = "murmurhash-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:23c56182822a1ed88e2a098ac56958dfec380696a9a943df203b9b41e4bcf5e4"}, - {file = "murmurhash-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:023391cfefe584ac544c1ea0936976c0119b17dd27bb8280652cef1704f76428"}, - {file = "murmurhash-1.0.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f00321998f0a6bad3fd068babf448a296d4b0b1f4dd424cab863ebe5ed54182f"}, - {file = "murmurhash-1.0.5-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:8381172e03c5f6f947005fb146a53c5e5a9e0d630be4a40cbf8838e9324bfe1c"}, - {file = "murmurhash-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fed7578fbaa6c301f27ed80834c1f7494ea7d335e269e98b9aee477cf0b3b487"}, - {file = "murmurhash-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d4c3a0242014cf4c84e9ea0ba3f13b48f02a3992de3da7b1116d11b816451195"}, - {file = "murmurhash-1.0.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:99e55488476a5f70e8d305fd31258f140e52f724f788bcc50c31ec846a2b3766"}, - {file = "murmurhash-1.0.5-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:b9292c532538cf47846ca81056cfeab08b877c35fe7521d6524aa92ddcd833e2"}, - {file = "murmurhash-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:fd17973fd4554715efd8d86b3e9200358e49e437fdb92a897ca127aced48b61c"}, - {file = "murmurhash-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:81474a45c4074637a6dfc8fea4cdebf091ab5aa781c2cfcb94c43b16030badd7"}, - {file = "murmurhash-1.0.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a9bd2312996e6e47605af305a1e5f091eba1bdd637cdd9986aec4885cb4c5530"}, - {file = "murmurhash-1.0.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:892749023da26420d194f37bfa30df1368aaac0149cfa3b2105db36b66549e37"}, - {file = "murmurhash-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:add366944eb8ec73013a4f36e166c5a4f0f7628ffe1746bc5fe031347489e5e8"}, - {file = "murmurhash-1.0.5.tar.gz", hash = "sha256:98ec9d727bd998a35385abd56b062cf0cca216725ea7ec5068604ab566f7e97f"}, + {file = "murmurhash-1.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814d559afe2a97ad40accf21ce96e8b04a3ff5a08f80c02b7acd427dbb7d567"}, + {file = "murmurhash-1.0.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c7b8cc4a8db1c821b80f8ca70a25c3166b14d68ecef8693a117c6a0b1d74ace"}, + {file = "murmurhash-1.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:e40790fdaf65213d70da4ed9229f16f6d6376310dc8fc23eacc98e6151c6ae7e"}, + {file = "murmurhash-1.0.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a78d53f047c3410ce4c589d9b47090f628f844ed5694418144e63cfe7f3da7e9"}, + {file = "murmurhash-1.0.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d69cc0ffc0ef6d37399b8a0484a44f9877e531ebc164e55105e89738ed52089"}, + {file = "murmurhash-1.0.6-cp36-cp36m-win_amd64.whl", hash = "sha256:8de08d145c85bb7ba89cb1b591742e3ef54cede73e35f62752af687a4a1859f7"}, + {file = "murmurhash-1.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7dc5a79346afa07f14384926c335c0c455226d687d1305b9378264875b450e51"}, + {file = "murmurhash-1.0.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab326b172dc470331490bda516d4d6d7578c91445ad83a2a3418ac1b9c5f9f55"}, + {file = "murmurhash-1.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:2911bc3e8040dfaac536b141539b0351915f1439953f0aa9e957f082cff035a6"}, + {file = "murmurhash-1.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de267459d040c96727ba141075d5bc983ec69c6f75b6df1b703e3b5cd7090382"}, + {file = "murmurhash-1.0.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90a8e06872015d6f9f66a42669e003a1df8be229defef69cd98546f4cb25546d"}, + {file = "murmurhash-1.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:773411eba268bf524c012e781f4405aacb9ef4edc063d1f6b38bbf06358b988e"}, + {file = "murmurhash-1.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f4ef3b26229ff192032a12653d637313e1231d23e788b83a2f4a3d8e2bf2d031"}, + {file = "murmurhash-1.0.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92cd7196974307143ce8e9e9b6e22e0a57abf30bdd5a1effe696b4825677e616"}, + {file = "murmurhash-1.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:cdd1036688341413e5adef32b3fd58e8b44f24405f394f90129f39ed879e4f24"}, + {file = "murmurhash-1.0.6.tar.gz", hash = "sha256:00a5252b569d3f914b5bd0bce72d2efe9c0fb91a9703556ea1b608b141c68f2d"}, ] mypy = [ {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, @@ -2437,20 +1730,9 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -nanotime = [ - {file = "nanotime-0.5.2.tar.gz", hash = "sha256:c7cc231fc5f6db401b448d7ab51c96d0a4733f4b69fabe569a576f89ffdf966b"}, -] -networkx = [ - {file = "networkx-2.6.3-py3-none-any.whl", hash = "sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef"}, - {file = "networkx-2.6.3.tar.gz", hash = "sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51"}, -] nltk = [ - {file = "nltk-3.6.3-py3-none-any.whl", hash = "sha256:665225d585367f64a73ff1cb436964b425304dc772406653412e19dfe0157688"}, - {file = "nltk-3.6.3.zip", hash = "sha256:5db64a976e88cef0db9200c7f25aeceba8a86efbe09cc824f01a6d3163f7e937"}, -] -nodeenv = [ - {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, - {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, + {file = "nltk-3.6.5-py3-none-any.whl", hash = "sha256:95fb4f577efe93af21765e9b2852235c2c6a405885da2a70f397478d94e906e0"}, + {file = "nltk-3.6.5.zip", hash = "sha256:834d1a8e38496369390be699be9bca4f2a0f2175b50327272b2ec7a98ffda2a0"}, ] numpy = [ {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, @@ -2490,105 +1772,43 @@ pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] -phonenumbers = [ - {file = "phonenumbers-8.12.32-py2.py3-none-any.whl", hash = "sha256:8a3c19c38852980961cc380b17ff8aeae844ccff7c4aae7a3789688f0351a628"}, - {file = "phonenumbers-8.12.32.tar.gz", hash = "sha256:c52c9c3607483072303ba8d8759063edc44d2f8fe7b85afef40bd8d1aafb6483"}, -] plac = [ {file = "plac-1.1.3-py2.py3-none-any.whl", hash = "sha256:487e553017d419f35add346c4c09707e52fa53f7e7181ce1098ca27620e9ceee"}, {file = "plac-1.1.3.tar.gz", hash = "sha256:398cb947c60c4c25e275e1f1dadf027e7096858fb260b8ece3b33bcff90d985f"}, ] platformdirs = [ - {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, - {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] -ply = [ - {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, - {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, -] -poyo = [ - {file = "poyo-0.5.0-py2.py3-none-any.whl", hash = "sha256:3e2ca8e33fdc3c411cd101ca395668395dd5dc7ac775b8e809e3def9f9fe041a"}, - {file = "poyo-0.5.0.tar.gz", hash = "sha256:e26956aa780c45f011ca9886f044590e2d8fd8b61db7b1c1cf4e0869f48ed4dd"}, -] -pre-commit = [ - {file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"}, - {file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"}, -] preshed = [ - {file = "preshed-3.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:572899224578d30f6a67fadecb3d62b824866b4d2b6bad73f71abf7585db1389"}, - {file = "preshed-3.0.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:67c11e384ce4c008bc487ba3a29bafdfe038b9a2546ccfe0fe2160480b356fed"}, - {file = "preshed-3.0.5-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:6e833f1632a1d0232bdc6df6c3542fb130ef044d8656b24576d9fd19e5f1e0d1"}, - {file = "preshed-3.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:1ce0846cb7ebb2ea913d44ec2e296098c285443ecdea80ddf02656bbef4deacb"}, - {file = "preshed-3.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8a560850b8c53c1487ba51c2b0f5769535512b36d3b129ad5796b64653abe2f9"}, - {file = "preshed-3.0.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6f126bcc414a0304b54956f9dac2628a0f9bef1657d1b3a3837fc82b791aa2a1"}, - {file = "preshed-3.0.5-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:1bdededa7fd81f26a42bc9d11d542657c74746b7ea7fc2b2ca6d0ddbf1f93792"}, - {file = "preshed-3.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9ebf444f8487782c84d7b5acb1d7195e603155882fafc4697344199eeeafbe5f"}, - {file = "preshed-3.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a3adffde3126c2a0ab7d57cab1d605cb5f63da1ba88088ad3cf8debfd9aa4dc"}, - {file = "preshed-3.0.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:56b9603517bb2a364418163236d6a147a1d722ff7546cbe085e76e25ae118e89"}, - {file = "preshed-3.0.5-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:5e06a49477bd257eea02bf823b5d3e201d00a19d6976523a58da8606b2358481"}, - {file = "preshed-3.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:ca4a7681b643b8356e7dfdab9cf668b2b34bd07ef4b09ebed44c8aeb3b1626ee"}, - {file = "preshed-3.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:85074eebf90a858a6b68242f1ae265ca99e1af45bf9dafcb9a83d49b0815a2e1"}, - {file = "preshed-3.0.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:12cbe1e378b4f1c6b06f5e4130408befe916e55ea1616e6aa63c5cd0ccd9c927"}, - {file = "preshed-3.0.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:30f0c8ea85113d0565a1e3eb6222d00513ec39b56f3f9a2615e304575e65422e"}, - {file = "preshed-3.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:fb4d2e82add82d63b2c97802b759a58ff200d06b632e2edc48a9ced1e6472faf"}, - {file = "preshed-3.0.5.tar.gz", hash = "sha256:c6d3dba39ed5059aaf99767017b9568c75b2d0780c3481e204b1daecde00360e"}, -] -psutil = [ - {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, - {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"}, - {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"}, - {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"}, - {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"}, - {file = "psutil-5.8.0-cp27-none-win32.whl", hash = "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"}, - {file = "psutil-5.8.0-cp27-none-win_amd64.whl", hash = "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"}, - {file = "psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"}, - {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"}, - {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"}, - {file = "psutil-5.8.0-cp36-cp36m-win32.whl", hash = "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"}, - {file = "psutil-5.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"}, - {file = "psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"}, - {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"}, - {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"}, - {file = "psutil-5.8.0-cp37-cp37m-win32.whl", hash = "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"}, - {file = "psutil-5.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"}, - {file = "psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"}, - {file = "psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"}, - {file = "psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"}, - {file = "psutil-5.8.0-cp38-cp38-win32.whl", hash = "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"}, - {file = "psutil-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"}, - {file = "psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"}, - {file = "psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"}, - {file = "psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"}, - {file = "psutil-5.8.0-cp39-cp39-win32.whl", hash = "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"}, - {file = "psutil-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"}, - {file = "psutil-5.8.0.tar.gz", hash = "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"}, + {file = "preshed-3.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a9683730127658b531120b4ed5cff1f2a567318ab75e9ab0f22cc84ae1486c23"}, + {file = "preshed-3.0.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c98f725d8478f3ade4ab1ea00f50a92d2d9406d37276bc46fd8bab1d47452c4"}, + {file = "preshed-3.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:ea8aa9610837e907e8442e79300df0a861bfdb4dcaf026a5d9642a688ad04815"}, + {file = "preshed-3.0.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e03ae3eee961106a517fcd827b5a7c51f7317236b3e665c989054ab8dc381d28"}, + {file = "preshed-3.0.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58661bea8d0d63a648588511407285e43d43627e27f836e30819801fb3c75d70"}, + {file = "preshed-3.0.6-cp36-cp36m-win_amd64.whl", hash = "sha256:5f99837e7353ce1fa81f0074d4b15f36e0af5af60a2a54d4d11e13cb09768a9e"}, + {file = "preshed-3.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8c60a400babfc5b25ba371fda7041be227f7c625e1fb7a43329c2c08fe00a53b"}, + {file = "preshed-3.0.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61b2ea656cb1c38d544cc774f1c2ad1cdab23167b46b35310a7e211d4ba9c6d0"}, + {file = "preshed-3.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:87e1add41b7f6236a3ccc34788f47ab8682bc28e8a2d369089062e274494c1a0"}, + {file = "preshed-3.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a279c138ad1d5be02547b1545254929588414b01571fe637016367f6a1aa11de"}, + {file = "preshed-3.0.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3af09f4cfcdaca085fd87dac8107617c4e2bb0ad1458f953841b71e9728287f5"}, + {file = "preshed-3.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:f92e752a868ea2690e1b38c4b775251a145e0fce36b9bdd972539e8271b7a23a"}, + {file = "preshed-3.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eaffbc71fdb8625f9aac4fe7e19e20bf318d1421ea05903bebe3e6ffef27b587"}, + {file = "preshed-3.0.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfe1495fcfc7f479de840ddc4f426dbb55351e218ae5c8712c1269183a4d0060"}, + {file = "preshed-3.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:92a8f49d17a63537a8beed48a049b62ef168ca07e0042a5b2bcdf178a1fb5d48"}, + {file = "preshed-3.0.6.tar.gz", hash = "sha256:fb3b7588a3a0f2f2f1bf3fe403361b2b031212b73a37025aea1df7215af3772a"}, ] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] -pyasn1 = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, - {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, - {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, -] pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] pycountry = [ {file = "pycountry-20.7.3.tar.gz", hash = "sha256:81084a53d3454344c0292deebc20fcd0a1488c136d4900312cbd465cf552cb42"}, @@ -2597,43 +1817,29 @@ pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] -pydot = [ - {file = "pydot-1.4.2-py2.py3-none-any.whl", hash = "sha256:66c98190c65b8d2e2382a441b4c0edfdb4f4c025ef9cb9874de478fb0793a451"}, - {file = "pydot-1.4.2.tar.gz", hash = "sha256:248081a39bcb56784deb018977e428605c1c758f10897a339fce1dd728ff007d"}, -] -pyflakes = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, -] -pygit2 = [ - {file = "pygit2-1.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:547429774c11f5bc9d20a49aa86e4bd13c90a55140504ef05f55cf424470ee34"}, - {file = "pygit2-1.6.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e75865d7b6fc161d93b16f10365eaad353cd546e302a98f2de2097ddea1066b"}, - {file = "pygit2-1.6.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4a64b6090308ffd1c82e2dd4316cb79483715387b13818156d516134a5b17c"}, - {file = "pygit2-1.6.1-cp36-cp36m-win32.whl", hash = "sha256:2666a3970b2ea1222a9f0463b466f98c8d564f29ec84cf0a58d9b0d3865dbaaf"}, - {file = "pygit2-1.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2de12ca2d3b7eb86106223b40b2edc0c61103c71e7962e53092c6ddef71a194"}, - {file = "pygit2-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9c1d96c66fb6e69ec710078a73c19edff420bc1db430caa9e03a825eede3f25c"}, - {file = "pygit2-1.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:454d42550fa6a6cd0e6a6ad9ab3f3262135fd157f57bad245ce156c36ee93370"}, - {file = "pygit2-1.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce0827b77dd2f8a3465bdc181c4e65f27dd12dbd92635c038e58030cc90c2de0"}, - {file = "pygit2-1.6.1-cp37-cp37m-win32.whl", hash = "sha256:b0161a141888d450eb821472fdcdadd14a072ddeda841fee9984956d34d3e19d"}, - {file = "pygit2-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:af2fa259b6f7899227611ab978c600695724e85965836cb607d8b1e70cfea9b3"}, - {file = "pygit2-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0e1e02c28983ddc004c0f54063f3e46fca388225d468e32e16689cfb750e0bd6"}, - {file = "pygit2-1.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5dadc4844feb76cde5cc9a37656326a361dd8b5c8e8f8674dcd4a5ecf395db3"}, - {file = "pygit2-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef07458e4172a31318663295083b43f957d611145738ff56aa76db593542a6e8"}, - {file = "pygit2-1.6.1-cp38-cp38-win32.whl", hash = "sha256:7a0c0a1f11fd41f57e8c6c64d903cc7fa4ec95d15592270be3217ed7f78eb023"}, - {file = "pygit2-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:2fd5c1b2d84dc6084f1bda836607afe37e95186a53a5a827a69083415e57fe4f"}, - {file = "pygit2-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9b88b7e9a5286a71be0b6c307f0523c9606aeedff6b61eb9c440e18817fa641"}, - {file = "pygit2-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac12d32b714c3383ebccffee5eb6aff0b69a2542a40a664fd5ad370afcb28ee7"}, - {file = "pygit2-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe682ed6afd2ab31127f6a502cf3e002dc1cc8d26c36a5d49dfd180250351eb6"}, - {file = "pygit2-1.6.1-cp39-cp39-win32.whl", hash = "sha256:dbbf66a23860aa899949068ac9b503b4bc21e6063e8f53870440adbdc909405e"}, - {file = "pygit2-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:f90775afb11f69376e2af21ab56fcfbb52f6bc84117059ddf0355f81e5e36352"}, - {file = "pygit2-1.6.1.tar.gz", hash = "sha256:c3303776f774d3e0115c1c4f6e1fc35470d15f113a7ae9401a0b90acfa1661ac"}, -] -pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, -] -pygtrie = [ - {file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"}, +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, ] pylint = [ {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"}, @@ -2647,14 +1853,14 @@ pytest = [ {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] +pytest-asyncio = [ + {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, + {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}, +] pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] -python-benedict = [ - {file = "python-benedict-0.24.2.tar.gz", hash = "sha256:7c19aea66d864204f9d190381f0bedb043e5a087826fcfd5fc09ee6f53a6cf3d"}, - {file = "python_benedict-0.24.2-py3-none-any.whl", hash = "sha256:bca095b5a97069f308a749502a06db07ce72a3a8d464f0200d42a519e624e932"}, -] python-crfsuite = [ {file = "python-crfsuite-0.9.7.tar.gz", hash = "sha256:3b4538d2ce5007e4e42005818247bf43ade89ef08a66d158462e2f7c5d63cee7"}, {file = "python_crfsuite-0.9.7-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:cd18b340c5a45ec200e8ce1167318dfc5d915ca9aad459dfa8675d014fd30650"}, @@ -2692,139 +1898,91 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-fsutil = [ - {file = "python-fsutil-0.5.0.tar.gz", hash = "sha256:02a347540d10c1616390a536ced73fd67df8d01c499f497d0ab3de3fbb236f0e"}, - {file = "python_fsutil-0.5.0-py3-none-any.whl", hash = "sha256:dc8de800c9915e6a3333ff2e917207a1a7cc20d0e7ea0e165473fa29e16be566"}, -] -python-slugify = [ - {file = "python-slugify-5.0.2.tar.gz", hash = "sha256:f13383a0b9fcbe649a1892b9c8eb4f8eab1d6d84b84bb7a624317afa98159cab"}, - {file = "python_slugify-5.0.2-py2.py3-none-any.whl", hash = "sha256:6d8c5df75cd4a7c3a2d21e257633de53f52ab0265cd2d1dc62a730e8194a7380"}, +python-dotenv = [ + {file = "python-dotenv-0.19.1.tar.gz", hash = "sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8"}, + {file = "python_dotenv-0.19.1-py2.py3-none-any.whl", hash = "sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a"}, ] pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, -] -pywin32 = [ - {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, - {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, - {file = "pywin32-301-cp36-cp36m-win32.whl", hash = "sha256:c866f04a182a8cb9b7855de065113bbd2e40524f570db73ef1ee99ff0a5cc2f0"}, - {file = "pywin32-301-cp36-cp36m-win_amd64.whl", hash = "sha256:dafa18e95bf2a92f298fe9c582b0e205aca45c55f989937c52c454ce65b93c78"}, - {file = "pywin32-301-cp37-cp37m-win32.whl", hash = "sha256:98f62a3f60aa64894a290fb7494bfa0bfa0a199e9e052e1ac293b2ad3cd2818b"}, - {file = "pywin32-301-cp37-cp37m-win_amd64.whl", hash = "sha256:fb3b4933e0382ba49305cc6cd3fb18525df7fd96aa434de19ce0878133bf8e4a"}, - {file = "pywin32-301-cp38-cp38-win32.whl", hash = "sha256:88981dd3cfb07432625b180f49bf4e179fb8cbb5704cd512e38dd63636af7a17"}, - {file = "pywin32-301-cp38-cp38-win_amd64.whl", hash = "sha256:8c9d33968aa7fcddf44e47750e18f3d034c3e443a707688a008a2e52bbef7e96"}, - {file = "pywin32-301-cp39-cp39-win32.whl", hash = "sha256:595d397df65f1b2e0beaca63a883ae6d8b6df1cdea85c16ae85f6d2e648133fe"}, - {file = "pywin32-301-cp39-cp39-win_amd64.whl", hash = "sha256:87604a4087434cd814ad8973bd47d6524bd1fa9e971ce428e76b62a5e0860fdf"}, + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] pyyaml = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] regex = [ - {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"}, - {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"}, - {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"}, - {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"}, - {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"}, - {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"}, - {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"}, - {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"}, - {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"}, - {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"}, - {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"}, - {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"}, - {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"}, - {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"}, - {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"}, - {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"}, + {file = "regex-2021.10.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edff4e31d159672a7b9d70164b21289e4b53b239ce1dc945bf9643d266537573"}, + {file = "regex-2021.10.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6432daf42f2c487b357e1aa0bdc43193f050ff53a3188bfab20b88202b53027"}, + {file = "regex-2021.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:468de52dd3f20187ab5ca4fd265c1bea61a5346baef01ad0333a5e89fa9fad29"}, + {file = "regex-2021.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5a2ac760f2fc13a1c58131ec217779911890899ce1a0a63c9409bd23fecde6f"}, + {file = "regex-2021.10.21-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ad1fedca001fefc3030d1e9022b038af429e58dc06a7e9c55e40bd1f834582ec"}, + {file = "regex-2021.10.21-cp310-cp310-win32.whl", hash = "sha256:9c613d797a3790f6b12e78a61e1cd29df7fc88135218467cf8b0891353292b9c"}, + {file = "regex-2021.10.21-cp310-cp310-win_amd64.whl", hash = "sha256:678d9a4ce79e1eaa4ebe88bc9769df52919eb30c597576a0deba1f3cf2360e65"}, + {file = "regex-2021.10.21-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2044174af237bb9c56ecc07294cf38623ee379e8dca14b01e970f8b015c71917"}, + {file = "regex-2021.10.21-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98743a2d827a135bf3390452be18d95839b947a099734d53c17e09a64fc09480"}, + {file = "regex-2021.10.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f1b23304855303bd97b5954edab63b8ddd56c91c41c6d4eba408228c0bae95f3"}, + {file = "regex-2021.10.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c4fd59747236423016ccd89b9a6485d958bf1aa7a8a902a6ba28029107a87f"}, + {file = "regex-2021.10.21-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:130a002fa386c976615a2f6d6dff0fcc25da24858994a36b14d2e3129dce7de2"}, + {file = "regex-2021.10.21-cp36-cp36m-win32.whl", hash = "sha256:8bd83d9b8ee125350cd666b55294f4bc9993c4f0d9b1be9344a318d0762e94cc"}, + {file = "regex-2021.10.21-cp36-cp36m-win_amd64.whl", hash = "sha256:98fe0e1b07a314f0a86dc58af4e717c379d48a403eddd8d966ab9b8bf91ce164"}, + {file = "regex-2021.10.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ded4748c7be6f31fb207387ee83a3a0f625e700defe32f268cb1d350ed6e4a66"}, + {file = "regex-2021.10.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3da121de36a9ead0f32b44ea720ee8c87edbb59dca6bb980d18377d84ad58a3"}, + {file = "regex-2021.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9dfba513eae785e3d868803f5a7e21a032cb2b038fa4a1ea7ec691037426ad3"}, + {file = "regex-2021.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ff91696888755e96230138355cbe8ce2965d930d967d6cff7c636082d038c78"}, + {file = "regex-2021.10.21-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0f82de529d7595011a40573cc0f27422e59cafa94943b64a4d17d966d75f2c01"}, + {file = "regex-2021.10.21-cp37-cp37m-win32.whl", hash = "sha256:164e51ace4d00f07c519f85ec2209e8faaeab18bc77be6b35685c18d4ac1c22a"}, + {file = "regex-2021.10.21-cp37-cp37m-win_amd64.whl", hash = "sha256:e39eafa854e469d7225066c806c76b9a0acba5ff5ce36c82c0224b75e24888f2"}, + {file = "regex-2021.10.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:740a28580520b099b804776db1e919360fcbf30a734a14c5985d5e39a39e7237"}, + {file = "regex-2021.10.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de7dbf72ae80f06e79444ff9614fb5e3a7956645d513b0e12d1bbe6f3ccebd11"}, + {file = "regex-2021.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dc1a9bedf389bf3d3627a4d2b21cbdc5fe5e0f029d1f465972f4437833dcc946"}, + {file = "regex-2021.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cd14f22425beecf727f6dbdf5c893e46ecbc5ff16197c16a6f38a9066f2d4d5"}, + {file = "regex-2021.10.21-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b75a3db3aab0bfa51b6af3f820760779d360eb79f59e32c88c7fba648990b4f"}, + {file = "regex-2021.10.21-cp38-cp38-win32.whl", hash = "sha256:f68c71aabb10b1352a06515e25a425a703ba85660ae04cf074da5eb91c0af5e5"}, + {file = "regex-2021.10.21-cp38-cp38-win_amd64.whl", hash = "sha256:c0f49f1f03be3e4a5faaadc35db7afa2b83a871943b889f9f7bba56e0e2e8bd5"}, + {file = "regex-2021.10.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:201890fdc8a65396cfb6aa4493201353b2a6378e27d2de65234446f8329233cb"}, + {file = "regex-2021.10.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1bfc6b7347de9f0ae1fb6f9080426bed6a9ca55b5766fa4fdf7b3a29ccae9c"}, + {file = "regex-2021.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:72a0b98d41c4508ed23a96eef41090f78630b44ba746e28cd621ecbe961e0a16"}, + {file = "regex-2021.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b5a0660a63b0703380758a7141b96cc1c1a13dee2b8e9c280a2522962fd12af"}, + {file = "regex-2021.10.21-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f82d3adde46ac9188db3aa7e6e1690865ebb6448d245df5a3ea22284f70d9e46"}, + {file = "regex-2021.10.21-cp39-cp39-win32.whl", hash = "sha256:bc4637390235f1e3e2fcdd3e904ca0b42aa655ae28a78072248b2992b4ad4c08"}, + {file = "regex-2021.10.21-cp39-cp39-win_amd64.whl", hash = "sha256:74d03c256cf0aed81997e87be8e24297b5792c9718f3a735f5055ddfad392f06"}, + {file = "regex-2021.10.21.tar.gz", hash = "sha256:4832736b3f24617e63dc919ce8c4215680ba94250a5d9e710fcc0c5f457b5028"}, ] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] -rich = [ - {file = "rich-10.10.0-py3-none-any.whl", hash = "sha256:0b8cbcb0b8d476a7f002feaed9f35e51615f673c6c291d76ddf0c555574fd3c7"}, - {file = "rich-10.10.0.tar.gz", hash = "sha256:bacf58b25fea6b920446fe4e7abdc6c7664c4530c4098e7a1bc79b16b8551dfa"}, -] -"ruamel.yaml" = [ - {file = "ruamel.yaml-0.17.16-py3-none-any.whl", hash = "sha256:ea21da1198c4b41b8e7a259301cc9710d3b972bf8ba52f06218478e6802dd1f1"}, - {file = "ruamel.yaml-0.17.16.tar.gz", hash = "sha256:1a771fc92d3823682b7f0893ad56cb5a5c87c48e62b5399d6f42c8759a583b33"}, -] -"ruamel.yaml.clib" = [ - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, - {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, -] scikit-learn = [ {file = "scikit-learn-0.19.2.tar.gz", hash = "sha256:b276739a5f863ccacb61999a3067d0895ee291c95502929b2ae56ea1f882e888"}, {file = "scikit_learn-0.19.2-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:66bfc2b6b15db1725d03ea657ec9184ff09dcbf1ecd834ef85f2edc2c9cbba97"}, @@ -2876,14 +2034,6 @@ scipy = [ {file = "scipy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a5193a098ae9f29af283dcf0041f762601faf2e595c0db1da929875b7570353f"}, {file = "scipy-1.6.1.tar.gz", hash = "sha256:c4fceb864890b6168e79b0e714c585dbe2fd4222768ee90bc1aa0f8218691b11"}, ] -shortuuid = [ - {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, - {file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"}, -] -shtab = [ - {file = "shtab-1.4.1-py2.py3-none-any.whl", hash = "sha256:22beab27e7a1587b25887f10f42734413c7ca13bae7cab462387916fb3e95895"}, - {file = "shtab-1.4.1.tar.gz", hash = "sha256:84454cb20caa0fd322c20715ae0b2410d48ea0b64f4aa470ba45c993cadbd91a"}, -] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2892,10 +2042,6 @@ sklearn-crfsuite = [ {file = "sklearn-crfsuite-0.3.6.tar.gz", hash = "sha256:2f59aad3055e01a778a79a6352891cac04788e8b52688aa5bc8b11be7717861e"}, {file = "sklearn_crfsuite-0.3.6-py2.py3-none-any.whl", hash = "sha256:6e9a42bc3de96941d5f7262335130955b8c380b1356147622368f385075705d9"}, ] -smmap = [ - {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, - {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, -] spacy = [ {file = "spacy-2.3.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ebc5a7f7da70a793cc1d2569097bdf80fbce66d7e39e17a157f95c258bec78d"}, {file = "spacy-2.3.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc59c5cf8f363d7c1dfd779bae7eafe99dc98a74063267f9f8df4154b65f7a2f"}, @@ -2911,10 +2057,6 @@ spacy = [ {file = "spacy-2.3.7-cp39-cp39-win_amd64.whl", hash = "sha256:abddbf424233a842aec91119bc8c4578890c587809d33f353f334a5383d8f490"}, {file = "spacy-2.3.7.tar.gz", hash = "sha256:c0f2315fea23497662e28212f89af3a03667f97c867c597b599c37ab84092e22"}, ] -speedcopy = [ - {file = "speedcopy-2.1.0-py3-none-any.whl", hash = "sha256:b80926ff900c0d7c8a0cb5f0a407258dde83976d923e3a449ac5417aa6608f63"}, - {file = "speedcopy-2.1.0.tar.gz", hash = "sha256:8bb1a6c735900b83901a7be84ba2175ed3887c13c6786f97dea48f2ea7d504c2"}, -] srsly = [ {file = "srsly-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a696e9c925e91f76ec53840c55483a4fbf76cb717424410a4f249d4805439038"}, {file = "srsly-1.0.5-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:8fc4c0641537262e15c7b5b57edc47487b15ac47b696adcb81e0a770ef78e8f5"}, @@ -2930,14 +2072,14 @@ srsly = [ {file = "srsly-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:334f29435099e644a8047b63d60b8386a98b5f7b4739f7efc86b46ca0200aa0e"}, {file = "srsly-1.0.5.tar.gz", hash = "sha256:d3dd796372367c71946d0cd6f734e49db3d99dd13a57bdac937d9eb62689fc9e"}, ] +starlette = [ + {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, + {file = "starlette-0.14.2.tar.gz", hash = "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"}, +] tabulate = [ {file = "tabulate-0.8.9-py3-none-any.whl", hash = "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4"}, {file = "tabulate-0.8.9.tar.gz", hash = "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7"}, ] -text-unidecode = [ - {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, - {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, -] thinc = [ {file = "thinc-7.4.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5774007b5c52501cab5e2970cadca84923b4c420fff06172f2d0c86531973ce8"}, {file = "thinc-7.4.5-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:02b71ae5a0fa906a0aca968bd65589e0ab9fabd511e57be839774228b1509224"}, @@ -2957,6 +2099,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomli = [ + {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, + {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, +] tox = [ {file = "tox-3.24.4-py2.py3-none-any.whl", hash = "sha256:5e274227a53dc9ef856767c21867377ba395992549f02ce55eb549f9fb9a8d10"}, {file = "tox-3.24.4.tar.gz", hash = "sha256:c30b57fa2477f1fb7c36aa1d83292d5c2336cd0018119e1b1c17340e2c2708ca"}, @@ -2965,38 +2111,6 @@ tqdm = [ {file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"}, {file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"}, ] -typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, -] typer = [ {file = "typer-0.4.0-py3-none-any.whl", hash = "sha256:d81169725140423d072df464cad1ff25ee154ef381aaf5b8225352ea187ca338"}, {file = "typer-0.4.0.tar.gz", hash = "sha256:63c3aeab0549750ffe40da79a1b524f60e08a2cbc3126c520ebf2eeaf507f5dd"}, @@ -3007,16 +2121,16 @@ typing-extensions = [ {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] urllib3 = [ - {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] -virtualenv = [ - {file = "virtualenv-20.8.0-py2.py3-none-any.whl", hash = "sha256:a4b987ec31c3c9996cf1bc865332f967fe4a0512c41b39652d6224f696e69da5"}, - {file = "virtualenv-20.8.0.tar.gz", hash = "sha256:4da4ac43888e97de9cf4fdd870f48ed864bbfd133d2c46cbdec941fed4a25aef"}, +uvicorn = [ + {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"}, + {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"}, ] -voluptuous = [ - {file = "voluptuous-0.12.1-py3-none-any.whl", hash = "sha256:8ace33fcf9e6b1f59406bfaf6b8ec7bcc44266a9f29080b4deb4fe6ff2492386"}, - {file = "voluptuous-0.12.1.tar.gz", hash = "sha256:663572419281ddfaf4b4197fd4942d181630120fb39b333e3adad70aeb56444b"}, +virtualenv = [ + {file = "virtualenv-20.8.1-py2.py3-none-any.whl", hash = "sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300"}, + {file = "virtualenv-20.8.1.tar.gz", hash = "sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8"}, ] wasabi = [ {file = "wasabi-0.8.2-py3-none-any.whl", hash = "sha256:a493e09d86109ec6d9e70d040472f9facc44634d4ae6327182f94091ca73a490"}, @@ -3027,63 +2141,126 @@ wcwidth = [ {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] werkzeug = [ - {file = "Werkzeug-2.0.1-py3-none-any.whl", hash = "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"}, - {file = "Werkzeug-2.0.1.tar.gz", hash = "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42"}, -] -win-unicode-console = [ - {file = "win_unicode_console-0.5.zip", hash = "sha256:d4142d4d56d46f449d6f00536a73625a871cba040f0bc1a2e305a04578f07d1e"}, + {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, + {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, ] wrapt = [ - {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, -] -xmltodict = [ - {file = "xmltodict-0.12.0-py2.py3-none-any.whl", hash = "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051"}, - {file = "xmltodict-0.12.0.tar.gz", hash = "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21"}, + {file = "wrapt-1.13.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3de7b4d3066cc610054e7aa2c005645e308df2f92be730aae3a47d42e910566a"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:8164069f775c698d15582bf6320a4f308c50d048c1c10cf7d7a341feaccf5df7"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9adee1891253670575028279de8365c3a02d3489a74a66d774c321472939a0b1"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a70d876c9aba12d3bd7f8f1b05b419322c6789beb717044eea2c8690d35cb91b"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3f87042623530bcffea038f824b63084180513c21e2e977291a9a7e65a66f13b"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e634136f700a21e1fcead0c137f433dde928979538c14907640607d43537d468"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3e33c138d1e3620b1e0cc6fd21e46c266393ed5dae0d595b7ed5a6b73ed57aa0"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:283e402e5357e104ac1e3fba5791220648e9af6fb14ad7d9cc059091af2b31d2"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ccb34ce599cab7f36a4c90318697ead18312c67a9a76327b3f4f902af8f68ea1"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fbad5ba74c46517e6488149514b2e2348d40df88cd6b52a83855b7a8bf04723f"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:724ed2bc9c91a2b9026e5adce310fa60c6e7c8760b03391445730b9789b9d108"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:83f2793ec6f3ef513ad8d5b9586f5ee6081cad132e6eae2ecb7eac1cc3decae0"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:0473d1558b93e314e84313cc611f6c86be779369f9d3734302bf185a4d2625b1"}, + {file = "wrapt-1.13.2-cp35-cp35m-win32.whl", hash = "sha256:15eee0e6fd07f48af2f66d0e6f2ff1916ffe9732d464d5e2390695296872cad9"}, + {file = "wrapt-1.13.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bc85d17d90201afd88e3d25421da805e4e135012b5d1f149e4de2981394b2a52"}, + {file = "wrapt-1.13.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6ee5f8734820c21b9b8bf705e99faba87f21566d20626568eeb0d62cbeaf23c"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:53c6706a1bcfb6436f1625511b95b812798a6d2ccc51359cd791e33722b5ea32"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fbe6aebc9559fed7ea27de51c2bf5c25ba2a4156cf0017556f72883f2496ee9a"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:0582180566e7a13030f896c2f1ac6a56134ab5f3c3f4c5538086f758b1caf3f2"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:bff0a59387a0a2951cb869251257b6553663329a1b5525b5226cab8c88dcbe7e"}, + {file = "wrapt-1.13.2-cp36-cp36m-win32.whl", hash = "sha256:df3eae297a5f1594d1feb790338120f717dac1fa7d6feed7b411f87e0f2401c7"}, + {file = "wrapt-1.13.2-cp36-cp36m-win_amd64.whl", hash = "sha256:1eb657ed84f4d3e6ad648483c8a80a0cf0a78922ef94caa87d327e2e1ad49b48"}, + {file = "wrapt-1.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0cdedf681db878416c05e1831ec69691b0e6577ac7dca9d4f815632e3549580"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:87ee3c73bdfb4367b26c57259995935501829f00c7b3eed373e2ad19ec21e4e4"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3e0d16eedc242d01a6f8cf0623e9cdc3b869329da3f97a15961d8864111d8cf0"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:8318088860968c07e741537030b1abdd8908ee2c71fbe4facdaade624a09e006"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d90520616fce71c05dedeac3a0fe9991605f0acacd276e5f821842e454485a70"}, + {file = "wrapt-1.13.2-cp37-cp37m-win32.whl", hash = "sha256:22142afab65daffc95863d78effcbd31c19a8003eca73de59f321ee77f73cadb"}, + {file = "wrapt-1.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d0d717e10f952df7ea41200c507cc7e24458f4c45b56c36ad418d2e79dacd1d4"}, + {file = "wrapt-1.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:593cb049ce1c391e0288523b30426c4430b26e74c7e6f6e2844bd99ac7ecc831"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8860c8011a6961a651b1b9f46fdbc589ab63b0a50d645f7d92659618a3655867"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ada5e29e59e2feb710589ca1c79fd989b1dd94d27079dc1d199ec954a6ecc724"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:fdede980273aeca591ad354608778365a3a310e0ecdd7a3587b38bc5be9b1808"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:af9480de8e63c5f959a092047aaf3d7077422ded84695b3398f5d49254af3e90"}, + {file = "wrapt-1.13.2-cp38-cp38-win32.whl", hash = "sha256:c65e623ea7556e39c4f0818200a046cbba7575a6b570ff36122c276fdd30ab0a"}, + {file = "wrapt-1.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:b20703356cae1799080d0ad15085dc3213c1ac3f45e95afb9f12769b98231528"}, + {file = "wrapt-1.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c5c4cf188b5643a97e87e2110bbd4f5bc491d54a5b90633837b34d5df6a03fe"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:82223f72eba6f63eafca87a0f614495ae5aa0126fe54947e2b8c023969e9f2d7"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:81a4cf257263b299263472d669692785f9c647e7dca01c18286b8f116dbf6b38"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:728e2d9b7a99dd955d3426f237b940fc74017c4a39b125fec913f575619ddfe9"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7574de567dcd4858a2ffdf403088d6df8738b0e1eabea220553abf7c9048f59e"}, + {file = "wrapt-1.13.2-cp39-cp39-win32.whl", hash = "sha256:c7ac2c7a8e34bd06710605b21dd1f3576764443d68e069d2afba9b116014d072"}, + {file = "wrapt-1.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e6d1a8eeef415d7fb29fe017de0e48f45e45efd2d1bfda28fc50b7b330859ef"}, + {file = "wrapt-1.13.2.tar.gz", hash = "sha256:dca56cc5963a5fd7c2aa8607017753f534ee514e09103a6c55d2db70b50e7447"}, ] yarl = [ - {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, - {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, - {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, - {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, - {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, - {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, - {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, - {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, - {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, - {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, - {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, - {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, - {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, -] -"zc.lockfile" = [ - {file = "zc.lockfile-2.0-py2.py3-none-any.whl", hash = "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"}, - {file = "zc.lockfile-2.0.tar.gz", hash = "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b"}, -] -zipp = [ - {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, - {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, + {file = "yarl-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e35d8230e4b08d86ea65c32450533b906a8267a87b873f2954adeaecede85169"}, + {file = "yarl-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb4b3f277880c314e47720b4b6bb2c85114ab3c04c5442c9bc7006b3787904d8"}, + {file = "yarl-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7015dcedb91d90a138eebdc7e432aec8966e0147ab2a55f2df27b1904fa7291"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb3e478175e15e00d659fb0354a6a8db71a7811a2a5052aed98048bc972e5d2b"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8c409aa3a7966647e7c1c524846b362a6bcbbe120bf8a176431f940d2b9a2e"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b22ea41c7e98170474a01e3eded1377d46b2dfaef45888a0005c683eaaa49285"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a7dfc46add4cfe5578013dbc4127893edc69fe19132d2836ff2f6e49edc5ecd6"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82ff6f85f67500a4f74885d81659cd270eb24dfe692fe44e622b8a2fd57e7279"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f3cd2158b2ed0fb25c6811adfdcc47224efe075f2d68a750071dacc03a7a66e4"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:59c0f13f9592820c51280d1cf811294d753e4a18baf90f0139d1dc93d4b6fc5f"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7f7655ad83d1a8afa48435a449bf2f3009293da1604f5dd95b5ddcf5f673bd69"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aa9f0d9b62d15182341b3e9816582f46182cab91c1a57b2d308b9a3c4e2c4f78"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fdd1b90c225a653b1bd1c0cae8edf1957892b9a09c8bf7ee6321eeb8208eac0f"}, + {file = "yarl-1.7.0-cp310-cp310-win32.whl", hash = "sha256:7c8d0bb76eabc5299db203e952ec55f8f4c53f08e0df4285aac8c92bd9e12675"}, + {file = "yarl-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:622a36fa779efb4ff9eff5fe52730ff17521431379851a31e040958fc251670c"}, + {file = "yarl-1.7.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d461b7a8e139b9e4b41f62eb417ffa0b98d1c46d4caf14c845e6a3b349c0bb1"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81cfacdd1e40bc931b5519499342efa388d24d262c30a3d31187bfa04f4a7001"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:821b978f2152be7695d4331ef0621d207aedf9bbd591ba23a63412a3efc29a01"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b64bd24c8c9a487f4a12260dc26732bf41028816dbf0c458f17864fbebdb3131"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:98c9ddb92b60a83c21be42c776d3d9d5ec632a762a094c41bda37b7dfbd2cd83"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a532d75ca74431c053a88a802e161fb3d651b8bf5821a3440bc3616e38754583"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:053e09817eafb892e94e172d05406c1b3a22a93bc68f6eff5198363a3d764459"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:98c51f02d542945d306c8e934aa2c1e66ba5e9c1c86b5bf37f3a51c8a747067e"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:15ec41a5a5fdb7bace6d7b16701f9440007a82734f69127c0fbf6d87e10f4a1e"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a7f08819dba1e1255d6991ed37448a1bf4b1352c004bcd899b9da0c47958513d"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8e3ffab21db0542ffd1887f3b9575ddd58961f2cf61429cb6458afc00c4581e0"}, + {file = "yarl-1.7.0-cp36-cp36m-win32.whl", hash = "sha256:50127634f519b2956005891507e3aa4ac345f66a7ea7bbc2d7dcba7401f41898"}, + {file = "yarl-1.7.0-cp36-cp36m-win_amd64.whl", hash = "sha256:36ec44f15193f6d5288d42ebb8e751b967ebdfb72d6830983838d45ab18edb4f"}, + {file = "yarl-1.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ec1b5a25a25c880c976d0bb3d107def085bb08dbb3db7f4442e0a2b980359d24"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b36f5a63c891f813c6f04ef19675b382efc190fd5ce7e10ab19386d2548bca06"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38173b8c3a29945e7ecade9a3f6ff39581eee8201338ee6a2c8882db5df3e806"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba402f32184f0b405fb281b93bd0d8ab7e3257735b57b62a6ed2e94cdf4fe50"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:be52bc5208d767cdd8308a9e93059b3b36d1e048fecbea0e0346d0d24a76adc0"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:08c2044a956f4ef30405f2f433ce77f1f57c2c773bf81ae43201917831044d5a"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:484d61c047c45670ef5967653a1d0783e232c54bf9dd786a7737036828fa8d54"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b7de92a4af85cfcaf4081f8aa6165b1d63ee5de150af3ee85f954145f93105a7"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:376e41775aab79c5575534924a386c8e0f1a5d91db69fc6133fd27a489bcaf10"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:8a8b10d0e7bac154f959b709fcea593cda527b234119311eb950096653816a86"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f46cd4c43e6175030e2a56def8f1d83b64e6706eeb2bb9ab0ef4756f65eab23f"}, + {file = "yarl-1.7.0-cp37-cp37m-win32.whl", hash = "sha256:b28cfb46140efe1a6092b8c5c4994a1fe70dc83c38fbcea4992401e0c6fb9cce"}, + {file = "yarl-1.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9624154ec9c02a776802da1086eed7f5034bd1971977f5146233869c2ac80297"}, + {file = "yarl-1.7.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:69945d13e1bbf81784a9bc48824feb9cd66491e6a503d4e83f6cd7c7cc861361"}, + {file = "yarl-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:46a742ed9e363bd01be64160ce7520e92e11989bd4cb224403cfd31c101cc83d"}, + {file = "yarl-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb4ff1ac7cb4500f43581b3f4cbd627d702143aa6be1fdc1fa3ebffaf4dc1be5"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ad51e17cd65ea3debb0e10f0120cf8dd987c741fe423ed2285087368090b33d"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e37786ea89a5d3ffbbf318ea9790926f8dfda83858544f128553c347ad143c6"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c63c1e208f800daad71715786bfeb1cecdc595d87e2e9b1cd234fd6e597fd71d"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91cbe24300c11835ef186436363352b3257db7af165e0a767f4f17aa25761388"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e510dbec7c59d32eaa61ffa48173d5e3d7170a67f4a03e8f5e2e9e3971aca622"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3def6e681cc02397e5d8141ee97b41d02932b2bcf0fb34532ad62855eab7c60e"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:263c81b94e6431942b27f6f671fa62f430a0a5c14bb255f2ab69eeb9b2b66ff7"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e78c91faefe88d601ddd16e3882918dbde20577a2438e2320f8239c8b7507b8f"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:22b2430c49713bfb2f0a0dd4a8d7aab218b28476ba86fd1c78ad8899462cbcf2"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e7ad9db939082f5d0b9269cfd92c025cb8f2fbbb1f1b9dc5a393c639db5bd92"}, + {file = "yarl-1.7.0-cp38-cp38-win32.whl", hash = "sha256:3a31e4a8dcb1beaf167b7e7af61b88cb961b220db8d3ba1c839723630e57eef7"}, + {file = "yarl-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:d579957439933d752358c6a300c93110f84aae67b63dd0c19dde6ecbf4056f6b"}, + {file = "yarl-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:87721b549505a546eb003252185103b5ec8147de6d3ad3714d148a5a67b6fe53"}, + {file = "yarl-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1fa866fa24d9f4108f9e58ea8a2135655419885cdb443e36b39a346e1181532"}, + {file = "yarl-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d3b8449dfedfe94eaff2b77954258b09b24949f6818dfa444b05dbb05ae1b7e"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db2372e350794ce8b9f810feb094c606b7e0e4aa6807141ac4fadfe5ddd75bb0"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a06d9d0b9a97fa99b84fee71d9dd11e69e21ac8a27229089f07b5e5e50e8d63c"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3455c2456d6307bcfa80bc1157b8603f7d93573291f5bdc7144489ca0df4628"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d30d67e3486aea61bb2cbf7cf81385364c2e4f7ce7469a76ed72af76a5cdfe6b"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c18a4b286e8d780c3a40c31d7b79836aa93b720f71d5743f20c08b7e049ca073"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d54c925396e7891666cabc0199366ca55b27d003393465acef63fd29b8b7aa92"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:64773840952de17851a1c7346ad7f71688c77e74248d1f0bc230e96680f84028"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:acbf1756d9dc7cd0ae943d883be72e84e04396f6c2ff93a6ddeca929d562039f"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:2e48f27936aa838939c798f466c851ba4ae79e347e8dfce43b009c64b930df12"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1beef4734ca1ad40a9d8c6b20a76ab46e3a2ed09f38561f01e4aa2ea82cafcef"}, + {file = "yarl-1.7.0-cp39-cp39-win32.whl", hash = "sha256:8ee78c9a5f3c642219d4607680a4693b59239c27a3aa608b64ef79ddc9698039"}, + {file = "yarl-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:d750503682605088a14d29a4701548c15c510da4f13c8b17409c4097d5b04c52"}, + {file = "yarl-1.7.0.tar.gz", hash = "sha256:8e7ebaf62e19c2feb097ffb7c94deb0f0c9fab52590784c8cd679d30ab009162"}, ] diff --git a/pylintrc b/pylintrc deleted file mode 100644 index ae9f8bb..0000000 --- a/pylintrc +++ /dev/null @@ -1,384 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,lstm.py - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" - -disable= - attribute-defined-outside-init, - duplicate-code, - fixme, - invalid-name, - missing-docstring, - protected-access, - too-few-public-methods, - super-init-not-called, - locally-disabled, - len-as-condition, - unnecessary-pass, - no-else-return, - no-else-raise, - inconsistent-return-statements, - arguments-differ, - too-many-statements, - too-many-nested-blocks, - anomalous-backslash-in-string, - # handled by black - format - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=2000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=__.*__ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# List of decorators that define properties, such as abc.abstractproperty. -property-classes=abc.abstractproperty - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis -ignored-modules=tensorflow - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). -ignored-classes=SQLObject, optparse.Values, thread._local, _thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members=REQUEST,acl_users,aq_parent - -# List of decorators that create context managers from functions, such as -# contextlib.contextmanager. -contextmanager-decorators=contextlib.contextmanager - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=10 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=25 - -# Maximum number of return / yield for function / method body -max-returns=11 - -# Maximum number of branch for function / method body -max-branches=26 - -# Maximum number of statements in function / method body -max-statements=100 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=11 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=25 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/pyproject.toml b/pyproject.toml index e472167..e99eb9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,45 +1,52 @@ [tool.poetry] -name = "webex-assistant-sdk" -version = "1.0.0" +name = "webex-skills" +version = "2.0.0" homepage = "https://github.com/cisco/webex-assistant-sdk" -description = "An SDK for developing applications for Webex Assistant." +description = "An SDK for skills for the Webex Assistant." readme = "README.md" -authors = ["Minh Vo Thanh ", "J.J. Jackson "] +authors = [ + "James Snow ", + "Juan Rodriguez ", + "Christian Mata ", + "Brady Lenz " +] license = "MIT" keywords = ['webex', 'webex-assistant', 'webex-teams'] classifiers = [ - 'Development Status :: 2 - Pre-Alpha', + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.8', ] [tool.poetry.dependencies] -python = "^3.7" -# cryptography maintains backward compatibility for 3 minor versions -# and each 10th minor release is major (2.8 -> 2.9 -> 3.0 ...) -cryptography = ">=2.8,<3.4" -mindmeld = "^4.3.5rc11" +python = "^3.8" +cryptography = "^35.0.0" +mindmeld = {version= "^4.3.5rc11", optional=true} requests = "^2.22.0" -spacy = "^2.3.0" -cookiecutter = "^1.7.2" typer = "^0.4.0" +pydantic = {extras = ["dotenv"], version = "^1.8.2"} +fastapi = "^0.68.1" [tool.poetry.dev-dependencies] -black = '^20.8b0' -flake8 = '^3.7.8' +black = '^21.9b0' isort = {version = '^4.3', extras = ['toml']} -pre-commit = '^1.18' pylint = '^2.3.1' pytest = '^5.1' pytest-cov = "^2.8.1" tox = "^3.14.0" autopep8 = "^1.5.7" +mock = "^4.0.3" +pytest-asyncio = "^0.15.1" +uvicorn = "^0.15.0" + +[tool.poetry.extras] +mindmeld = ["mindmeld"] [tool.poetry.scripts] -wxa_sdk = 'webex_assistant_sdk.cli:main' +webex-skills = 'webex_skills.cli:main' [tool.autopep8] max_line_length = 120 diff --git a/tests/conftest.py b/tests/conftest.py index a84e18b..2283e26 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,40 +1,23 @@ -import os +import re -from mindmeld import NaturalLanguageProcessor import pytest -from webex_assistant_sdk.app import SkillApplication -from webex_assistant_sdk.dialogue import SkillResponder +from webex_assistant_sdk.dialogue.rules import SimpleDialogueStateRule +from webex_assistant_sdk.models.mindmeld import DialogueState -from .skill import app +pytestmark = pytest.mark.asyncio -@pytest.fixture(name='skill_dir') -def _skill_dir(): - return os.path.join(os.path.realpath(os.path.dirname(__file__)), 'skill') +@pytest.fixture() +def dialogue_state(): + return DialogueState( + text='test', + context={}, + params={'time_zone': 'thing', 'timestamp': 12345, 'language': 'en'}, + frame={}, + ) @pytest.fixture -def responder(): - return SkillResponder() - - -@pytest.fixture(name='skill_nlp') -def _skill_nlp(skill_dir) -> NaturalLanguageProcessor: - """Provides a built processor instance""" - nlp = NaturalLanguageProcessor(app_path=skill_dir) - nlp.build() - nlp.dump() - return nlp - - -@pytest.fixture(name='skill_app') -def _skill_app(skill_nlp) -> SkillApplication: - app.lazy_init(nlp=skill_nlp) - return app - - -@pytest.fixture(name='client') -def _client(skill_app: SkillApplication): - server = skill_app._server.test_client() - yield server +def test_rule(): + return SimpleDialogueStateRule(re.compile('.*test.*')) diff --git a/tests/skill/README.md b/tests/skill/README.md deleted file mode 100644 index 6ef9caa..0000000 --- a/tests/skill/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a basic sample skill. This directory can by copied and used as the starting point for a new skill implementation. diff --git a/tests/skill/__init__.py b/tests/skill/__init__.py deleted file mode 100644 index 20ba259..0000000 --- a/tests/skill/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module contains the SkillApplication demo application""" -from pathlib import Path - -from webex_assistant_sdk.app import SkillApplication -from webex_assistant_sdk.crypto import load_private_key_from_file - -secret = 'some secret' -key = load_private_key_from_file(str(Path(__file__).resolve().parent / 'id_rsa.pem')) -app = SkillApplication(__name__, secret=secret, private_key=key) - -__all__ = ['app'] - - -@app.introduce -@app.handle(intent='greet') -def welcome(request, responder): - del request - responder.reply('Hello. I am a third party skill. What would you like to do?') - responder.listen() - - -@app.handle(intent='exit') -def say_goodbye(request, responder): - del request - responder.reply(['Bye', 'Goodbye', 'Have a nice day.']) - responder.sleep() - - -@app.handle(intent='help') -def provide_help(request, responder): - del request - prompts = ["I can help you with your personal tasks.'"] - responder.reply(prompts) - responder.listen() diff --git a/tests/skill/__main__.py b/tests/skill/__main__.py deleted file mode 100644 index 955a88b..0000000 --- a/tests/skill/__main__.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module defines the command line interface for this app. Use -`python -m ` to see available commands. -""" - -if __name__ == '__main__': - from . import app - - app.cli() diff --git a/tests/skill/domains/general/exit/test.txt b/tests/skill/domains/general/exit/test.txt deleted file mode 100644 index 26ebc4f..0000000 --- a/tests/skill/domains/general/exit/test.txt +++ /dev/null @@ -1,15 +0,0 @@ -Catch you later -Goodbye and have a good day. -Okay, you have a great day. -bye -bye-bye -Alright, I'm good now -Good-bye. -Great, that's a wrap -Nope, that's it. -OK, I think we are all done for now, bye. -abort -Thanks for that -finished -that's all I need! -that's everything I need. diff --git a/tests/skill/domains/general/exit/train.txt b/tests/skill/domains/general/exit/train.txt deleted file mode 100644 index e69f2bf..0000000 --- a/tests/skill/domains/general/exit/train.txt +++ /dev/null @@ -1,155 +0,0 @@ -Alright, I think that wraps it up. -Alright, I'll talk to you later. -Alright, I'm good now -Alright, gotta go -Alright, gotta go -Bye -Cancel -Exit -GOODBYE -Good day to you! -Good-bye. -Goodbye -Goodbye, Have a nice day! -Great, that's a wrap -Have a good day. -Have a nice day! -I am finished. -I guess we are done here. -I'm Out! -I'm done. That's all for now. -If I need anything else, I'll be back. -Later -Nice talking to you. -Nope, that's it. -OK, I think we are all done for now, bye. -OK, I'm going to go for now. -Ok, that was great, gotta go now! -Okay, I guess we are done here. -Okay, I think we've accomplished everything for now! -See ya later. -See you later. -See you soon! -Smell you later. -Talk to you later! -Thank you so much -Thank you so much -abort -Thanks so much -That about covers it. -That's all I needed -That's everything I need. -This sounds good, I'll talk to you later. -Unfortunately I have another engagement soon, but it was nice chatting with you. -You too -abort -abort -all done -bye, have a nice day! -bye, talk to you later! -cancel -done -done for now -end -exit -finish -finish shopping -finished -goodbye -have a nice day! -later -quit -see you -see you later -see you next time -see you soon -stop -stop it -that will be all -that'll do for now -that's all I need! -that's everything I need. -that's it. -this is the end -we are done here. -you too -Alright, I think that wraps it up. -Alright, I'll talk to you later. -Alright, gotta go -Alright, gotta go -Bye -Cancel -Exit -GOODBYE -Good day to you! -Goodbye -Goodbye and have a good day. -Goodbye, Have a nice day! -Have a good day. -Have a nice day! -I am finished. -I guess we are done here. -I'm Out! -I'm done. That's all for now. -If I need anything else, I'll be back. -Later -Nice talking to you. -OK, I'm going to go for now. -Ok, that was great, gotta go now! -Okay, I guess we are done here. -Okay, I think we've accomplished everything for now! -Okay, you have a great day. -See ya later. -See you later. -See you soon! -Smell you later. -Talk to you later! -Thank you so much -Thanks so much -Thanks -Thank you -Thanks for the info -Thank you for the information -Thanks. That was helpful. -Thank you for the help -Thanks! -Thank you! -Thanks. -Thank you. -That about covers it. -That's all I needed -That's everything I need. -This sounds good, I'll talk to you later. -Unfortunately I have another engagement soon, but it was nice chatting with you. -You too -abort -all done -bye -bye, have a nice day! -bye, talk to you later! -bye-bye -cancel -done -done for now -end -exit -finish -finish shopping -goodbye -have a nice day! -later -quit -see you -see you later -see you next time -see you soon -stop -stop it -that will be all -that'll do for now -that's it. -this is the end -this is the end -we are done here. -you too -that's all for now diff --git a/tests/skill/domains/general/greet/test.txt b/tests/skill/domains/general/greet/test.txt deleted file mode 100644 index 0e87142..0000000 --- a/tests/skill/domains/general/greet/test.txt +++ /dev/null @@ -1,47 +0,0 @@ -Good afternoon! -Good morning! -Greetings Earthling. -Hello, how would you like me to start? -Hey dude. -hey what's up? -Hi, lets get started -Hi! How are you? -hi How are you doing? -How are you today -How are you? -Howdy! -Salutations! -What are you up to? -what's up? -whatcha doing -Hello -hey -hi -hihi -Yo -Hey how's it going? -Are you there? -Hello, how are you? -Namasthe, How are you -Hey-o! -How are you on this fine day? -Ay, how are you? -Good afternoon -Hey buddy -Hey there -Hey what's goin on? -Hi, whatcha been up to? -Hi. How are you? -How's it hanging? -Yo what's up -Hi -Wake up -Hey now! -How are you doing? -Greetings -wassup -Hiya! How are you? -SUP MY FRIEND! -Hey! What's going on in your world today? -Morning -heya diff --git a/tests/skill/domains/general/greet/train.txt b/tests/skill/domains/general/greet/train.txt deleted file mode 100644 index 77d4b34..0000000 --- a/tests/skill/domains/general/greet/train.txt +++ /dev/null @@ -1,509 +0,0 @@ -Aloha! -Are you there? -Ay, how are you? -Bonjour -Dude! how are you -G’day mate! -Good afternoon -Good afternoon! -Good day! -Good day! -Good evening! -Good morning -Good morning! -Good morning! -Good morning. How are you? -Good to see you, how have you been? -Great seeing you! -Greetings -Greetings my friend, how are you? -Greetings, ready to start the day? -Greetings! -Greetings! -Greetings! -Greetings! -Greetings. -hello -Hello and how are you doing? -hello there -Hello there! -Hello there! -Hello there. -Hello to you! -Hello, how are you? -Hello, how are you? -Hello, what you up to? -Hello! -Hello! Hope your day is going well! -Hello. How are you today? -Hello. How are you? -hey -Hey buddy -hey hey hey -Hey how are you? -Hey how's it going? -HEY HOWS IT GOING -Hey man -Hey now! -hey there -Hey there -Hey there, How are you? -Hey there, How are you? -Hey There, Sup! -Hey there! -hey wassup -Hey what's goin on? -Hey you -Hey you! -Hey-o! -Hey, how's it going? -Hey, something new? -Hey, whatcha doing? -Hey, whatcha up to? -Hey, whats going on? -Hey! -Hey! What's going on in your world today? -Heya, how's it going? -hi -hi -Hi how are you? -hi there -Hi there, how're you doing? -Hi there! -Hi, are we ready to start? -Hi, can you help me with something? -Hi, how are you doing today? -Hi, how are you doing? -Hi, How are you today? -Hi, how are you? -Hi, how's it going? -HI, how's it going? -Hi, how's it going? -Hi, I was wondering if you could help me with something. -Hi, whatcha been up to? -Hi,nice to be speaking with you. -Hi! -Hi! -Hi! How are you? -Hi. How are you? -Hiya! How are you? -hola -Hola! -How are ya doing? -How are you doing today? -How are you doing? -hi there How are you doing? -hey How are you doing? -How are you on this fine day? -How are you today? -How are you today? -How are you? -How goes it? -How have you been today? -how is it going? -How is your doing going so far? -How'd there! -How's everything? -How's it going? -How's it going? -How's it going? -How's it hanging? -How's it hanging? -Howdy there -Howdy! -Howdy! -Hows it going? -Morning -Namasthe, How are you -Oh hello -ola -Salutations my friend -Sam, how's it going? -Sup -SUP MY FRIEND! -Sup! -Sup!? -wassup -Welcome. -What are you up do? -What up? -What's going on? -What's up -whats good stranger! -Whats happening? -Yo -yo -Yo man, Hows it going. -Yo what's up -Yo yo yo -Yo, what's good? -Yo, what's up? -Hey -Hey man -Hey -Heyhey -Heyhey -Hi -Hi iq -Hi -Hiya -How are you? -How are you? -Poke -Wake up -hello -hey man -hiya -what's up -yo -hiya -heey -begin -dudehello -hey there -hey -hello there -hi there -hey wassup -hey what's up? -yo -hola -heya -hiya -hihi -hihi -hihi -heeyHi! -Yo yo yo -Hey there! -Hi! -How are you today -hi -Hello there! -How's it going? -Howdy! -How are you? -how is it going? -Hi, how are you doing? -Greetings. -Sup!? -Good morning -Hey! -Hey man -What up? -Hi, how's it going? -Hey you! -Greetings! -Sup -Hey now! -Good morning! -How are you doing? -Good morning! -Hola! -How are you? -Howdy! -Greetings -Hi how are you? -What's up -hey hey hey -Bonjour -wassup -Hey you -Greetings my friend, how are you? -Greetings Earthling. -Howdy! -Hows it going? -Dude! how are you -What's going on? -Hey dude. -Hello to you! -Hey, whatcha up to? -Good afternoon -Hello! -Hiya! How are you? -ola -How have you been today? -Good evening! -Good day! -Greetings! -Yo, what's good? -Yo, what's up? -Good morning. How are you? -Salutations! -Sup! -Salutations my friend -Welcome. -SUP MY FRIEND! -What are you up to? -Great seeing you! -whatcha doing -How are ya doing? -How is your doing going so far? -Hey buddy -Good morning! -What are you up do? -How's it hanging? -Aloha! -Hello, what you up to? -Whats happening? -How are you doing today? -How goes it? -Hey! What's going on in your world today? -Hi, whatcha been up to? -whats good stranger! -Hey, something new? -Oh hello -Ay, how are you? -How's everything? -Yo man, Hows it going. -Good to see you, how have you been? -G’day mate! -How's it hanging? -Morning -heyaAloha! -Bonjour -Dude! how are you -G’day mate! -Good afternoon! -Good afternoon! -Good day! -Good day! -Good evening! -Good morning -Good morning! -Good morning! -Good morning! -Good morning. How are you? -Good to see you, how have you been? -Great seeing you! -Greetings -Greetings Earthling. -Greetings my friend, how are you? -Greetings, ready to start the day? -Greetings! -Greetings! -Greetings! -Greetings! -Greetings. -hello -Hello and how are you doing? -hello there -Hello there! -Hello there! -Hello there. -Hello to you! -Hello, how are you? -Hello, how are you? -Hello, how would you like me to start? -Hello, what you up to? -Hello! -Hello! Hope your day is going well! -Hello. How are you today? -Hello. How are you? -hey -Hey dude. -hey hey hey -Hey how are you? -Hey how's it going? -HEY HOWS IT GOING -Hey man -Hey now! -hey there -Hey there, How are you? -Hey there, How are you? -Hey There, Sup! -Hey there! -hey wassup -hey what's up? -Hey you -Hey you! -Hey-o! -Hey, how's it going? -Hey, something new? -Hey, whatcha doing? -Hey, whatcha up to? -Hey, whats going on? -Hey! -Hey! What's going on in your world today? -Heya, how's it going? -hi -Hi how are you? -hi there -Hi there, how're you doing? -Hi there! -Hi, are we ready to start? -Hi, can you help me with something? -Hi, how are you doing today? -Hi, how are you doing? -Hi, How are you today? -Hi, how are you? -Hi, how's it going? -HI, how's it going? -Hi, how's it going? -Hi, I was wondering if you could help me with something. -Hi,nice to be speaking with you. -Hi! -Hi! -Hi! How are you? -Hi! How are you? -Hiya! How are you? -hola -Hola! -How are ya doing? -How are you doing today? -How are you doing? -How are you on this fine day? -How are you today -How are you today? -How are you today? -How are you? -How are you? -How goes it? -How have you been today? -how is it going? -How is your doing going so far? -How'd there! -How's everything? -How's it going? -How's it going? -How's it going? -How's it hanging? -Howdy there -Howdy! -Howdy! -Howdy! -Hows it going? -Morning -Namasthe, How are you -Oh hello -ola -Salutations my friend -Salutations! -Sam, how's it going? -Sup -SUP MY FRIEND! -Sup! -Sup!? -wassup -Welcome. -What are you up do? -What are you up to? -What up? -What's going on? -What's up -whatcha doing -whats good stranger! -Whats happening? -Yo -yo -Yo man, Hows it going. -Yo yo yo -Yo, what's good? -Yo, what's up? -Hello -Hey -Hey man -Hey -Heyhey -Heyhey -Hi -Hi iq -Hiya -How are you? -How are you? -Poke -hello -hey man -hey -hi -hiya -what's up -yo -hihi -hiya -heey -begin -Hi! -Yo yo yo -Hey there! -Hi! -Yo -Hey how's it going? -How are you today -hi -Hello there! -How's it going? -Howdy! -How are you? -how is it going? -Hi, how are you doing? -Greetings. -Sup!? -Are you there? -Good morning -Hey! -Hey man -What up? -Hi, how's it going? -Hey you! -Greetings! -Sup -Good morning! -Hola! -How are you? -Howdy! -Hi how are you? -What's up -hey hey hey -Bonjour -Hey you -Greetings my friend, how are you? -Greetings Earthling. -Howdy! -Hows it going? -Dude! how are you -What's going on? -Hey dude. -Hello to you! -Hey, whatcha up to? -Good afternoon -Hello! -Hello, how are you? -ola -How have you been today? -Good evening! -Good day! -Greetings! -Yo, what's good? -Yo, what's up? -Namasthe, How are you -Good morning. How are you? -Salutations! -Sup! -Salutations my friend -Welcome. -What are you up to? -Great seeing you! -whatcha doing -Hey-o! -How are ya doing? -How is your doing going so far? -Hey buddy -What are you up do? -How's it hanging? -Aloha! -Hello, what you up to? -Whats happening? -How are you doing today? -How goes it? -Hi, whatcha been up to? -whats good stranger! -Hey, something new? -Oh hello -Ay, how are you? -How's everything? -Yo man, Hows it going. -Good to see you, how have you been? -G’day mate! -How's it hanging? -how are you doing diff --git a/tests/skill/domains/general/help/test.txt b/tests/skill/domains/general/help/test.txt deleted file mode 100644 index 6bd9463..0000000 --- a/tests/skill/domains/general/help/test.txt +++ /dev/null @@ -1,10 +0,0 @@ -real person please -please help -human person -human person please -human representative -What -I am confused. -What are you? -Get me some help. -I am lost. diff --git a/tests/skill/domains/general/help/train.txt b/tests/skill/domains/general/help/train.txt deleted file mode 100644 index b8140e5..0000000 --- a/tests/skill/domains/general/help/train.txt +++ /dev/null @@ -1,96 +0,0 @@ -help -please help -mayday -mayday -customer service -support -support agent -agent -real person -real person -human -human person -human person -human person please -human representative -human representative -human representative -human representative please -i'm stuck -What? -Help -Say what? -What -help -what -what can i do -How do I contact a human representative? -I want to speak to a representative? -Can I talk to a real person? -what is the customer support phone line? -How do I get a hold of customer support? -Customer support, please. -What is the number to reach a customer support agent directly? -How can I get the customer support. -Contact customer support? -Offer online customer support? -What is the customer support phone number? -Hi, I need some help today. -Hi, how are you today? Was wondering if you could help me with an issue.help -mayday -customer service -support -support agent -agent -real person -real person -real person please -human -human person -human representative -human representative -human representative please -i'm stuck -What? -Help -Say what? -help -what -what can i do -How do I contact a human representative? -I want to speak to a representative? -Can I talk to a real person? -what is the customer support phone line? -How do I get a hold of customer support? -Customer support, please. -What is the number to reach a customer support agent directly? -How can I get the customer support. -Contact customer support? -Offer online customer support? -What is the customer support phone number? -Hi, I need some help today. -Hi, how are you today? Was wondering if you could help me with an issue. -What can I say? -I don't know what to say. -I am a bit confused here. -I'm confused. -This is confusing. Can you help me? -What do I ask? -How can I ask questions? -What can you do? -Who are you? -How do I use you? -What can I ask? -How can you help me? -I need help. -I don't understand. -I don't know what to do. -Help me. -I'm lost. -What is this? -Teach me. -Teach me how to use you. -User guide -Tutorial -User manual -Support diff --git a/tests/skill/id_rsa.pem b/tests/skill/id_rsa.pem deleted file mode 100644 index 49ec596..0000000 --- a/tests/skill/id_rsa.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -M4gk4KT3N1Wrj5UiYJl1NJvUnfaroQ== ------END PRIVATE KEY----- diff --git a/tests/skill/id_rsa.pub b/tests/skill/id_rsa.pub deleted file mode 100644 index 185f1f2..0000000 --- a/tests/skill/id_rsa.pub +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6asNr/BaaPv0JBtkeH6g -ozLVIdYj6+Wnir1A+5l9h8xuN9G3P0ROcfImsMbYP1+E2ZJxIlEs4aMPcca3qD9Q -L1ebbP0ge3XOB+BkrKTaDgt6EFHfRkEieSbg/RmH34wTUSn1GyOrEa/oe5RJD+uI -YlwHayFq9WkbWg8lc3I0DpuEFSumAWH2EPesqZNR6XeeccVLNARZaab84BDlHiiV -K74Za9wgi8zHdvXDRHoRUxmlQbMgIIP9WkE654ACXDa5uBtmq0DTLy0L/aLkrFMC -paePJf/w5xwhlXFR1mDmxl3PSC82oqllBaI2sSrZVRO2ffLWsvn1fJypT45/+Idh -p2ViA3iQ3vbqv37KFr2XiaQwL2mCX4/b9rTTlVZX71g/BZibuwtZcnfecrNpT5TB -GahosomYPkJb/rs8pJ7Z21JpkhnoZSPe3zk4cf8PCvrmn8rjI6L+Q1Zc6ajyoJeZ -PTikG/QQdKaMWSEC/D1JL1HFLRt0xOCN7LtQBZr0yuyYHjryHoLJxhGe7phffJBI -ZvIRMsGiJjT1eF6RuZnk9rteDpZpdexyRrgWQnZLljMqu6y7691Wk+XY0pp2uCNW -aD28s2VwBH88ag8VCpYy0Ldu2NCS/z1OHX3YZScw8hmI8tnwWcYCuWp4pT59CnWC -l46mUL42y2sSpkv8CjyiEY8CAwEAAQ== ------END PUBLIC KEY----- diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 67ad731..3203449 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -6,16 +6,11 @@ from webex_assistant_sdk.crypto import sign_token, verify_signature -@pytest.fixture(scope='session', name='temp_dir') -def _temp_dir(tmp_path_factory): - return tmp_path_factory.mktemp("tmp") - - def test_signatures(): - secret = 'top secret' + secret = b'top secret' message = json.dumps({'hello': 'this is a message'}) - signature = sign_token(message, secret) + signature = sign_token(message, secret.decode('utf-8')) try: verify_signature(secret, message.encode('utf-8'), base64.b64decode(signature)) except Exception as exc: # pylint:disable=broad-except diff --git a/tests/test_dialogue.py b/tests/test_dialogue.py new file mode 100644 index 0000000..621e229 --- /dev/null +++ b/tests/test_dialogue.py @@ -0,0 +1,79 @@ +from mock import AsyncMock +import pytest + +from webex_assistant_sdk.dialogue.manager import MissingHandler, SimpleDialogueManager +from webex_assistant_sdk.dialogue.rules import SimpleDialogueStateRule +from webex_assistant_sdk.models.mindmeld import DialogueState + +pytestmark = pytest.mark.asyncio + + +async def test_rule_matching(dialogue_state, test_rule): + mock_handler = AsyncMock() + manager = SimpleDialogueManager(rules={test_rule: mock_handler}) + + state = DialogueState() + await manager.handle(state) + assert mock_handler.called + + +async def test_rule_ordering(dialogue_state: DialogueState, test_rule: SimpleDialogueStateRule): + mock_handler1 = AsyncMock() + mock_handler2 = AsyncMock() + test_rule2 = SimpleDialogueStateRule(test_rule.regex) + + manager = SimpleDialogueManager(rules={test_rule: mock_handler1, test_rule2: mock_handler2}) + await manager.handle(dialogue_state) + assert mock_handler1.called + assert not mock_handler2.called + + # Reorder and make sure the first is still called + mock_handler1.reset_mock() + mock_handler2.reset_mock() + + manager = SimpleDialogueManager(rules={test_rule: mock_handler2, test_rule2: mock_handler1}) + await manager.handle(dialogue_state) + assert mock_handler2.called + assert not mock_handler1.called + + +def test_add_rule(): + manager = SimpleDialogueManager() + + @manager.add_rule(pattern=".*test.*") + def test_func(state): + pass + + assert test_func in manager.rules.values() + + +def test_add_default(): + manager = SimpleDialogueManager() + + @manager.add_rule(default=True) + async def test_func(state): + pass + + assert test_func not in manager.rules.values() + assert manager.default_handler == test_func + + +async def test_no_match_with_default(dialogue_state: DialogueState): + manager = SimpleDialogueManager() + + default_mock = AsyncMock() + manager.add_rule(default=True)(default_mock) + + @manager.add_rule(pattern=".*test.*") + async def pattern_test(state): + pass + + dialogue_state.text = "something that won't match" + await manager.handle(dialogue_state) + assert default_mock.called + + +async def test_no_match_no_default(dialogue_state): + manager = SimpleDialogueManager() + with pytest.raises(MissingHandler): + await manager.handle(dialogue_state) diff --git a/tests/test_directives.py b/tests/test_directives.py new file mode 100644 index 0000000..e20e5ff --- /dev/null +++ b/tests/test_directives.py @@ -0,0 +1,50 @@ +from typing import Any, Dict, Optional + +from webex_assistant_sdk.dialogue import responses + + +def assert_format(response: responses.SkillDirective, payload: Optional[Dict[str, Any]] = None): + expected = {'name': response.name, 'type': response.type} + if payload: + expected['payload'] = payload + assert response.dict() == expected + + +def test_reply(): + assert_format(responses.Reply('test'), {'text': 'test'}) + + +def test_listen(): + assert_format(responses.Listen()) + + +def test_speak(): + assert_format(responses.Speak('sup'), {'text': 'sup'}) + + +def test_sleep(): + assert_format(responses.Sleep(10), {'delay': 10}) + assert_format(responses.Sleep(), {'delay': 0}) + + +def test_display_webview(): + url = 'https://cisco.com' + title = 'Cisco' + assert_format(responses.DisplayWebView(url=url, title=title), {'url': url, 'title': title}) + + +def test_clear_webview(): + assert_format(responses.ClearWebView()) + + +def test_ui_hint(): + hints = ['hint1', 'hint2'] + prompt = 'try' + display_immediately = False + + expected = {'texts': hints, 'prompt': prompt, 'display_immediately': display_immediately} + assert_format(responses.UIHint(texts=hints, prompt=prompt, display_immediately=display_immediately), expected) + + +def test_asr_hint(): + assert_format(responses.AsrHint(texts=['hint1', 'hint2']), {'texts': ['hint1', 'hint2']}) diff --git a/tests/test_responder.py b/tests/test_responder.py deleted file mode 100644 index 7607ab7..0000000 --- a/tests/test_responder.py +++ /dev/null @@ -1,120 +0,0 @@ -import pytest - -from webex_assistant_sdk.app import SkillResponder -from webex_assistant_sdk.dialogue import DirectiveFormatError - - -@pytest.mark.parametrize( - "query, remove_hyphens, text", - [ - ("is this room open for an hour", False, "is this room open for an hour"), - ("spark-assistant", True, "spark assistant"), - ("spark-assistant", False, "spark-assistant"), - ("speak-to webex-assistant", True, "speak to webex assistant"), - ], -) -def test_speak(responder: SkillResponder, query: str, remove_hyphens: bool, text: str): - responder.speak(text=query, remove_hyphens=remove_hyphens) - assert responder.directives[0]['payload']['text'] == text - assert responder.directives[0]['name'] == 'speak' - assert responder.directives[0]['type'] == 'action' - - -@pytest.mark.parametrize( - "key, value", - [('pload', 'view'), ('url', 'www.google.com'), (1, 2), ('texts', ['hello', 'world'])], -) -def test_display(responder: SkillResponder, key, value): - responder.display('display', {key: value}) - assert responder.directives[0]['payload'][key] == value - assert responder.directives[0]['name'] == 'display' - assert responder.directives[0]['type'] == 'view' - - -def test_display_web_view(responder: SkillResponder): - responder.display_web_view(url='some-url') - assert responder.directives[0]['name'] == 'display-web-view' - assert responder.directives[0]['payload']['url'] == 'some-url' - assert responder.directives[0]['type'] == 'action' - - -def test_ui_hints(responder: SkillResponder): - responder.ui_hints(['go back', 'see more'], prompt='speak carefully', display_immediately=True) - assert responder.directives[0]['name'] == 'ui-hint' - assert responder.directives[0]['payload']['text'] == ['go back', 'see more'] - assert responder.directives[0]['payload']['prompt'] == 'speak carefully' - assert responder.directives[0]['payload']['displayImmediately'] - assert responder.directives[0]['type'] == 'view' - - -def test_asr_hints(responder: SkillResponder): - responder.asr_hints(['go back', 'see more']) - assert responder.directives[0]['name'] == 'asr-hint' - assert responder.directives[0]['payload']['text'] == ['go back', 'see more'] - assert responder.directives[0]['type'] == 'action' - - -@pytest.mark.parametrize( - "query, remove_hyphens, text", - [ - ("is this room open for an hour", False, "is this room open for an hour"), - ("spark-assistant", True, "spark assistant"), - ("spark-assistant", False, "spark-assistant"), - ("speak-to webex-assistant", True, "speak to webex assistant"), - ], -) -def test_reply(responder: SkillResponder, query: str, remove_hyphens: bool, text: str): - responder.reply(query, is_spoken=True, remove_hyphens=remove_hyphens) - assert responder.directives[0]['payload']['text'] == query - assert responder.directives[0]['name'] == 'reply' - assert responder.directives[0]['type'] == 'view' - assert responder.directives[1]['payload']['text'] == text - assert responder.directives[1]['name'] == 'speak' - assert responder.directives[1]['type'] == 'action' - - -def test_reply_incrementing_group(responder: SkillResponder): - responder.reply('hello', increment_group=True, is_spoken=False) - responder.reply('world', increment_group=True, is_spoken=False) - assert responder.directives[0]['payload']['text'] == 'hello' - assert responder.directives[0]['payload']['group'] == 1 - assert responder.directives[0]['name'] == 'reply' - assert responder.directives[0]['type'] == 'view' - assert responder.directives[1]['payload']['text'] == 'world' - assert responder.directives[1]['payload']['group'] == 2 - assert responder.directives[1]['name'] == 'reply' - assert responder.directives[1]['type'] == 'view' - with pytest.raises(DirectiveFormatError) as ex: - responder.reply('again', increment_group=True, is_spoken=False) - assert 'reply directive can only support two groups.' in str(ex.value) - - -@pytest.mark.parametrize( - "query, remove_hyphens, text", - [ - ("is this room open for an hour", False, "is this room open for an hour"), - ("spark-assistant", True, "spark assistant"), - ("spark-assistant", False, "spark-assistant"), - ("speak-to webex-assistant", True, "speak to webex assistant"), - ], -) -def test_long_reply(responder: SkillResponder, query: str, remove_hyphens: bool, text: str): - responder.long_reply(query, is_spoken=True, remove_hyphens=remove_hyphens) - assert responder.directives[0]['payload']['text'] == query - assert responder.directives[0]['name'] == 'long-reply' - assert responder.directives[0]['type'] == 'view' - assert responder.directives[1]['payload']['text'] == text - assert responder.directives[1]['name'] == 'speak' - - -def test_assistant_event(responder: SkillResponder): - expected_name = 'test' - expected_payload = {'foo': 'bar'} - - responder.send_assistant_event(expected_name, expected_payload) - - directive = responder.directives[0] - - assert directive['name'] == responder.DirectiveNames.ASSISTANT_EVENT - assert directive['payload']['name'] == expected_name - assert directive['payload']['payload'] == expected_payload diff --git a/tests/test_skill_server.py b/tests/test_skill_server.py deleted file mode 100644 index 6839ad9..0000000 --- a/tests/test_skill_server.py +++ /dev/null @@ -1,80 +0,0 @@ -import json -import os -from pathlib import Path - -from webex_assistant_sdk.app import SkillApplication -from webex_assistant_sdk.crypto import ( - generate_token, - load_public_key_from_file, - prepare_payload, - sign_token, -) - - -def test_skill_intro(skill_app: SkillApplication): - response = skill_app.app_manager.parse('', params={'target_dialogue_state': 'skill_intro'}) - assert response.dialogue_state == 'skill_intro' - - -def test_parse_endpoint_fail(client): - test_request = {'text': 'hi'} - response = client.post( - '/parse', - data=json.dumps(test_request), - content_type='application/json', - follow_redirects=True, - ) - assert response.status_code == 403 - - response = client.post('/parse') - assert response.status_code == 403 - - -def test_parse_endpoint_success(skill_app, client): - test_request = {'text': 'hi', 'challenge': 'a challenge'} - - public_key = load_public_key_from_file( - str(Path(__file__).resolve().parent / 'skill/id_rsa.pub') - ) - payload = prepare_payload(json.dumps(test_request), public_key, skill_app.secret) - response = client.post( - '/parse', - data=json.dumps(payload), - content_type='application/json', - follow_redirects=True, - ) - assert response.status_code == 200 - response_data = json.loads(response.data.decode('utf8')) - assert response_data['dialogue_state'] == 'welcome' - assert response_data['challenge'] == 'a challenge' - # Use >= set comparison as newer versions of mindmeld may add fields - assert set(response_data.keys()) >= { - 'history', - 'params', - 'frame', - 'dialogue_state', - 'request_id', - 'response_time', - 'request', - 'directives', - 'slots', - 'challenge', - } - - -def test_health_endpoint(skill_app, client): - challenge = os.urandom(32).hex() - public_key = load_public_key_from_file( - str(Path(__file__).resolve().parent / 'skill/id_rsa.pub') - ) - token = generate_token(challenge, public_key) - signature = sign_token(token, skill_app.secret) - - query_params = { - 'signature': signature, - 'message': token - } - response = client.get('/parse', query_string=query_params) - assert response.status_code == 200 - resp_set = set(json.loads(response.data.decode('utf8')).keys()) - assert resp_set == {'api_version', 'challenge', 'status'} diff --git a/tox.ini b/tox.ini deleted file mode 100644 index bc49024..0000000 --- a/tox.ini +++ /dev/null @@ -1,17 +0,0 @@ -[flake8] -max-line-length = 100 - -[tox] -isolated_build = true -envlist = py37 - -[testenv] -whitelist_externals = - poetry -commands = - poetry install -v - poetry run pytest tests - poetry run black --check . - poetry run isort --check - poetry run flake8 webex_assistant_sdk tests - poetry run pylint webex_assistant_sdk tests diff --git a/webex_assistant_sdk/__init__.py b/webex_assistant_sdk/__init__.py deleted file mode 100644 index 0643d57..0000000 --- a/webex_assistant_sdk/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -from ._version import __version__ diff --git a/webex_assistant_sdk/_version.py b/webex_assistant_sdk/_version.py deleted file mode 100644 index 2906b70..0000000 --- a/webex_assistant_sdk/_version.py +++ /dev/null @@ -1,3 +0,0 @@ -version = (0, 4, 0) -api_version = (1, 0) -__version__ = '.'.join((str(i) for i in version)) diff --git a/webex_assistant_sdk/app.py b/webex_assistant_sdk/app.py deleted file mode 100644 index 1c52234..0000000 --- a/webex_assistant_sdk/app.py +++ /dev/null @@ -1,49 +0,0 @@ -from flask import Flask -from mindmeld import Application - -from .dialogue import SkillResponder -from .server import create_skill_server - - -class SkillApplication(Application): - """ - SkillApplication extends MindMeld application with the appropriate encryption protocols. - """ - - def __init__( - self, - import_name, - *, - secret, - private_key, - responder_class=SkillResponder, - **kwargs): - - super().__init__(import_name, responder_class=responder_class, **kwargs) - self.secret = secret - self.private_key = private_key - - def introduce(self, handler=None): - """Decorator for skill introduction states. If a skill is - invoked without any text this state will be used. - - Returns: - Callable: the decorated handler OR the decorator - - """ - decorator = self.handle(targeted_only=True, name='skill_intro') - - # If the handler is passed in decorate and return it - if handler and callable(handler): - return decorator(handler) - - # otherwise return the decorating function - return decorator - - def lazy_init(self, nlp=None): - Application.lazy_init(self, nlp) - self._server = create_skill_server(self.app_manager, self.secret, self.private_key) - - @property - def web_app(self) -> Flask: - return self._server diff --git a/webex_assistant_sdk/cli.py b/webex_assistant_sdk/cli.py deleted file mode 100644 index fa5d3b3..0000000 --- a/webex_assistant_sdk/cli.py +++ /dev/null @@ -1,152 +0,0 @@ -import json -import os -from pathlib import Path -from pprint import pprint -import re -import secrets -from typing import Optional - -from cookiecutter.main import cookiecutter -from cryptography.hazmat.primitives import serialization -import requests -import typer - -from webex_assistant_sdk import crypto - -app = typer.Typer() - - -@app.command() -def invoke( - secret: Optional[str] = typer.Option(None, '--secret', '-s'), - public_key_path: Optional[Path] = typer.Option(None, '-k', '--key'), - url: Optional[str] = typer.Option('http://localhost:8080', '-u'), -): - """Invoke a skill running locally or remotely""" - if not secret: - secret: str = typer.prompt('Secret', hide_input=True, type=str) - while not len(secret) > 20: - typer.echo('Secrets must be at least 20 characters') - secret = typer.prompt('Secret', hide_input=True) - - if not public_key_path: - public_key_path = Path(typer.prompt('Public key path', Path('./id_rsa.pub'))) - - if not public_key_path.exists(): - while 1: - typer.secho(f'The path {public_key_path} does not exist, please try again', color=typer.colors.RED) - public_key_path = Path(typer.prompt('Public key path', public_key_path)) - if public_key_path.exists(): - break - if not url: - url = typer.prompt('URL to invoke the skill', default=url) - - public_key_text = public_key_path.read_text(encoding='utf-8') - typer.echo('Enter commands below (Ctl+C to exit)') - query = typer.prompt('>>', prompt_suffix=' ') - - challenge = os.urandom(16).hex() - message = { - 'challenge': challenge, - 'text': query, - 'context': {}, - 'params': {}, - 'frame': {}, - 'history': [], - } - while True: - req = crypto.prepare_payload(json.dumps(message), public_key_text, secret) - resp = requests.post(url, json=req) - json_resp = resp.json() - - if not json_resp.get('challenge') == challenge: - typer.secho('Skill did not respond with expected challenge value', fg=typer.colors.RED, err=True) - - typer.echo(pprint(json_resp, indent=2, width=120)) - query = typer.prompt('>>', prompt_suffix=' ') - - challenge = os.urandom(16).hex() - message = { - 'challenge': challenge, - 'text': query, - 'context': json_resp.get('context', {}), - 'params': {}, - 'frame': json_resp.get('frame'), - 'history': json_resp.get('history'), - } - - -@app.command() -def generate_keys( - filepath: Optional[Path] = typer.Argument(None), - name: Optional[str] = 'id_rsa', - use_password: Optional[bool] = typer.Option(False, '-p', help='Use a password for the private key'), -): - """Generate an RSA keypair""" - if not filepath: - filepath = Path.cwd() - - typer.secho('🔐 Generating new RSA keypair...', fg=typer.colors.GREEN) - - if use_password: - password: str = typer.prompt('Password', hide_input=True, confirmation_prompt=True) - encryption = serialization.BestAvailableEncryption(password.encode('utf-8')) - else: - encryption = serialization.NoEncryption() - - priv_path = filepath / f'{name}.pem' - pub_path = filepath / f'{name}.pub' - - if priv_path.exists() or pub_path.exists(): - confirmation = typer.confirm(f'File exists, would you like to overwrite the files at {priv_path}') - if not confirmation: - return - typer.echo(f'Writing files {priv_path} and {pub_path} to {filepath.absolute()}') - crypto.generate_keys(encryption, priv_path, pub_path) - - -@app.command() -def generate_secret(): - """Generate a secret token for signing requests""" - typer.echo(secrets.token_urlsafe(16)) - - -@app.command(name='new') -def new_skill(skill_name: str, secret: Optional[str] = None, password: Optional[str] = None): - """Create a new skill project from a template""" - # TODO: Support key generation, rather than making assumptions about its name - # and location - invoke_location = Path().resolve() - package_location = Path(__file__).resolve() - - if not secret: - secret = secrets.token_urlsafe(16) - - # Format the skill_name; Do not allow spaces - skill_name = re.compile("[- ]+").sub('_', skill_name) - - # TODO: Add logic to use MM-less template when available - template_path = package_location.parent / 'templates/mindmeld_template' - - rsa_filename: str = f'{skill_name}.id_rsa' - cookiecutter( - str(template_path), - output_dir=str(invoke_location), - no_input=True, - extra_context={ - 'skill_name': skill_name, - 'rsa_file_name': rsa_filename, # Path to the RSA key - 'rsa_password': password, - 'app_secret': secret, - }, - ) - - Path(invoke_location / skill_name / skill_name / 'entities').mkdir() - - -def main(): - app() - - -if __name__ == '__main__': - main() diff --git a/webex_assistant_sdk/crypto.py b/webex_assistant_sdk/crypto.py deleted file mode 100644 index fc4f7a4..0000000 --- a/webex_assistant_sdk/crypto.py +++ /dev/null @@ -1,126 +0,0 @@ -import base64 -import binascii -from pathlib import Path -from typing import Union, cast - -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.fernet import Fernet -from cryptography.hazmat.primitives import hashes, hmac, serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric.padding import MGF1, OAEP -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey -from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, NoEncryption, load_pem_private_key - -from webex_assistant_sdk.exceptions import EncryptionKeyError - - -def decrypt(private_key: str, message: str) -> str: - key = load_private_key(private_key.encode("utf-8")) - - padding = OAEP(mgf=MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) - - encrypted_fernet_key, fernet_token = message.split(".") - encrypted_fernet_key_bytes = base64.b64decode(encrypted_fernet_key.encode("utf-8")) - - fernet_key = key.decrypt(encrypted_fernet_key_bytes, padding) - - fernet_token_bytes = base64.b64decode(fernet_token) - payload = Fernet(fernet_key).decrypt(fernet_token_bytes) - return payload.decode("utf-8") - - -def verify_signature(secret: str, message: bytes, signature: bytes) -> None: - secret_bytes = secret.encode("utf-8") - - sig = hmac.HMAC(secret_bytes, hashes.SHA256()) - sig.update(message) - sig.verify(signature) - - -def load_private_key(private_key_bytes: bytes): - """Loads a private key in PEM format""" - try: - private_key: RSAPrivateKey = load_pem_private_key(private_key_bytes, None) - return private_key - except (binascii.Error, ValueError, UnsupportedAlgorithm) as ex: - raise EncryptionKeyError('Unable to load private key') from ex - - -def load_private_key_from_file(filename: str) -> str: - key_data = Path(filename).read_bytes() - return key_data.decode('utf-8') - - -def load_public_key_from_file(filename: str) -> str: - key_data = Path(filename).read_bytes() - return key_data.decode('utf-8') - - -def encrypt_fernet_key(fernet_key: bytes, pub_key: bytes) -> bytes: - """Encrypts a fernet key with an RSA private key""" - padding = OAEP(mgf=MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) - public_key = cast(RSAPublicKey, serialization.load_pem_public_key(pub_key)) - return public_key.encrypt(fernet_key, padding) - - -def generate_token(message: str, pub_key: str) -> str: - # Encode our message and keys to bytes up front so it's clear what we're working with - pub_key_bytes = pub_key.encode('utf-8') - message_bytes = message.encode('utf-8') - - # Generate a temporary fernet key, essentially two 128 bit AES keys mushed together - fernet_key = Fernet.generate_key() - - # Encrypt our fernet key with our RSA public key - encrypted_fernet_key = encrypt_fernet_key(fernet_key, pub_key_bytes) - - # Encrypt our message using the temporary encryption key - encrypted_message = Fernet(fernet_key).encrypt(message_bytes) - - # Format our key/message combo for transmitting over the wire. b64encode the bytes then - # encode the base64 as a utf-8 string - encoded_fernet_key = base64.b64encode(encrypted_fernet_key).decode('utf-8') - encoded_message = base64.b64encode(encrypted_message).decode('utf-8') - - # Our final token format is a string of base64 bytes representing - # our key/message, delineated by a '.' - return f'{encoded_fernet_key}.{encoded_message}' - - -def prepare_payload(message: str, pub_key: str, secret: str) -> dict: - token = generate_token(message, pub_key) - signature = sign_token(token, secret) - - return { - 'signature': signature, - 'message': token, - } - - -def sign_token(message: str, secret: str) -> str: - secret_bytes = secret.encode('utf-8') - message_bytes = message.encode('utf-8') - sig = hmac.HMAC(secret_bytes, hashes.SHA256()) - sig.update(message_bytes) - sig_bytes = sig.finalize() - return base64.b64encode(sig_bytes).decode('utf-8') - - -def generate_keys(encryption: Union[BestAvailableEncryption, NoEncryption], priv_path: Path, pub_path: Path): - - private_key = rsa.generate_private_key(65537, 4096) - public_key: rsa.RSAPublicKey = private_key.public_key() - - priv_path.write_bytes( - private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=encryption, - ) - ) - - pub_path.write_bytes( - public_key.public_bytes( - encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo - ) - ) diff --git a/webex_assistant_sdk/dialogue.py b/webex_assistant_sdk/dialogue.py deleted file mode 100644 index 22e8b39..0000000 --- a/webex_assistant_sdk/dialogue.py +++ /dev/null @@ -1,251 +0,0 @@ -import uuid - -from mindmeld.components.dialogue import DialogueResponder, DirectiveNames - - -class AssistantDirectiveNames(DirectiveNames): - """A constants object for directive names.""" - - DISPLAY_WEB_VIEW = 'display-web-view' - """A directive to display a web view.""" - - CLEAR_WEB_VIEW = 'clear-web-view' - """A directive to clear a web view""" - - UI_HINT = 'ui-hint' - """A directive to display a UI hint.""" - - ASR_HINT = 'asr-hint' - """A directive to display an ASR hint.""" - - LONG_REPLY = 'long-reply' - """A directive to display long replies.""" - - DISPLAY = 'display' - """A generic display directive.""" - - ASSISTANT_EVENT = 'assistant-event' - """A directive to forward a generic payload""" - - -class DirectiveNotSupportedError(Exception): - pass - - -class DirectiveFormatError(Exception): - pass - - -class SkillResponder(DialogueResponder): - DirectiveNames = AssistantDirectiveNames - group_counter = 0 - - def direct(self, name, dtype, payload=None, did=None): # pylint: disable=arguments-differ - """Adds an arbitrary directive and return it. - - Args: - name (str): The name of the directive. - dtype (str): The type of the directive. - payload (dict, optional): The payload for the directive. - did (str): Directive id, for logging purpose. - - Returns: - (dict): Added directive. - """ - if not self.is_directive_supported(name): - raise DirectiveNotSupportedError - - directive = {'name': name, 'type': dtype} - - if payload: - directive['payload'] = payload - - if did: - directive['id'] = did - - self.directives.append(directive) - return directive - - def speak(self, text, remove_hyphens=False): # pylint: disable=arguments-differ - """Adds a 'speak' directive. - - Args: - text (str): The text to speak aloud. - remove_hyphens (bool): Should hyphens in the text be removed. - """ - text = self._process_template(text) - if remove_hyphens: - text = text.replace('-', ' ') - self.act(self.DirectiveNames.SPEAK, payload={'text': text}) - - def display(self, name, payload=None): - """Adds an arbitrary directive of type 'view' and return it. - - Args: - name (str): The name of the directive. - payload (dict, optional): The payload for the view. - - Returns: - (dict): added directive of type view. - """ - return self.direct(name, self.DirectiveTypes.VIEW, payload=payload, did=str(uuid.uuid4())) - - def display_web_view(self, url=None): - """Displays a web view. - - Args: - url (str): The url of the web view - """ - if url: - self.act(self.DirectiveNames.DISPLAY_WEB_VIEW, payload={'url': url}) - else: - self._logger.warning('No url is passed for display web view directive.') - - def ui_hints(self, templates, prompt=None, display_immediately=False): - """Sends a 'hint' view' - - Args: - templates (list): The list of hint templates. - prompt (string): An optional prompt to precede the hints. - display_immediately (boolean): Show hints as soon as possible? - """ - texts = [] - for template in templates: - template = self._choose(template) - texts.append(template.format(**self.slots)) - - payload = {'text': texts} - if prompt: - payload['prompt'] = prompt - if display_immediately: - payload['displayImmediately'] = True - self.display(self.DirectiveNames.UI_HINT, payload=payload) - - def asr_hints(self, templates): - """Sends asr hints which helps the ASR to recognize better. - - Args: - templates (list): The list of ASR templates/texts. - """ - texts = [] - for template in templates: - template = self._choose(template) - texts.append(template.format(**self.slots)) - payload = {'text': texts} - self.act(self.DirectiveNames.ASR_HINT, payload=payload) - - def clear_web_view(self): - """Dismisses the web view.""" - self.act(self.DirectiveNames.CLEAR_WEB_VIEW) - - def reply( # pylint: disable=arguments-differ - self, response_strings, increment_group=False, is_spoken=True, remove_hyphens=False - ): - """Sends a 'reply' view and a 'speak' directive. - - Args: - response_strings (ResponseStrings): A list of reply templates. - increment_group (bool): Should text belong to next group. - is_spoken (bool): Should the text be spoken. - remove_hyphens (bool): Should hyphens in the text be removed in speak directives. - """ - template = self._choose(response_strings) - text = template.format(**self.slots) - - # Send reply - success = self._reply(text, increment_group=increment_group) - - if is_spoken: - try: - self.speak(text=text, remove_hyphens=remove_hyphens) - success = True - except DirectiveNotSupportedError: - pass - - # If we sent neither reply or speak, raise error - if not success: - raise DirectiveNotSupportedError - - def _reply(self, text, increment_group=False): - """Sends a 'reply' view directive. - - Args: - text (str): Reply that should be displayed. - increment_group (bool, optional): Should text belong to next group. - - Returns: - (bool): If the 'reply' view was successfully added - """ - if increment_group: - self.group_counter += 1 - - if self.group_counter > 2: - raise DirectiveFormatError('reply directive can only support two groups.') - - payload = {'text': text, 'group': self.group_counter} - success = False - try: - self.display(self.DirectiveNames.REPLY, payload=payload) - success = True - except DirectiveNotSupportedError: - pass - - return success - - def long_reply( - self, response_strings: list, is_spoken: bool = True, remove_hyphens: bool = False - ): - """Sends a 'long-reply' view and a 'speak' directive. Used for replies - are too long to be sent as a normal 'reply' and which should be - formatted appropriately. - - Args: - response_strings (list): A list of reply templates. - is_spoken (bool): Should the text be spoken. - remove_hyphens (bool): Should hyphens in the text be removed in speak directives. - """ - template = self._choose(response_strings) - text = template.format(**self.slots) - payload = {'text': text} - success = False - - try: - self.display(self.DirectiveNames.LONG_REPLY, payload=payload) - success = True - except DirectiveNotSupportedError: - # Note: should we use a normal reply in these cases? - pass - - if is_spoken: - try: - self.speak(text=text, remove_hyphens=remove_hyphens) - success = True - except DirectiveNotSupportedError: - pass - - # If we sent neither long reply or speak, raise error. - if not success: - raise DirectiveNotSupportedError - - def send_assistant_event(self, name, payload=None): - """Sends a 'assistant-event' directive - - Args: - name (string): Used to identify the source of the event - payload (json object, optional): Payload to forward - """ - self.act(self.DirectiveNames.ASSISTANT_EVENT, { - 'name': name, - 'payload': payload, - }) - - @property - def supported_directives(self): - return [ - getattr(self.DirectiveNames, at) - for at in dir(self.DirectiveNames) - if not at.startswith('__') - ] - - def is_directive_supported(self, directive): - return directive in self.supported_directives diff --git a/webex_assistant_sdk/exceptions.py b/webex_assistant_sdk/exceptions.py deleted file mode 100644 index dd1bb74..0000000 --- a/webex_assistant_sdk/exceptions.py +++ /dev/null @@ -1,34 +0,0 @@ -class WebexAssistantSDKException(Exception): - pass - - -class EncryptionKeyError(WebexAssistantSDKException): - pass - - -class SignatureGenerationError(WebexAssistantSDKException): - pass - - -class RequestValidationError(WebexAssistantSDKException): - """An exception raised when request is invalid""" - - pass - - -class SignatureValidationError(RequestValidationError): - """An exception raised when signature validation fails""" - - pass - - -class ServerChallengeValidationError(RequestValidationError): - pass - - -class ResponseValidationError(WebexAssistantSDKException): - pass - - -class ClientChallengeValidationError(ResponseValidationError): - pass diff --git a/webex_assistant_sdk/helpers.py b/webex_assistant_sdk/helpers.py deleted file mode 100644 index 5e823a3..0000000 --- a/webex_assistant_sdk/helpers.py +++ /dev/null @@ -1,153 +0,0 @@ -import base64 -import json -import logging -import os -from typing import Mapping, Tuple, Union - -from cryptography.exceptions import InvalidSignature -import requests - -from . import crypto -from .exceptions import ( - ClientChallengeValidationError, - RequestValidationError, - ResponseValidationError, - ServerChallengeValidationError, - SignatureValidationError, -) - -logger = logging.getLogger(__name__) - - -def validate_request( - secret: str, - private_key: str, - body: Union[str, bytes]) -> Tuple[Mapping, str]: - """Validates a request to an agent - - Args: - secret (str): The configured secret for the skill - private_key (str): The private key for this skill - body (str or bytes): The request body - - Returns: - Tuple[Mapping, str]: The decrypted request body and a challenge string - - Raises: - RequestValidationError: raised when request data cannot be decrypted or decoded - ServerChallengeValidationError: raised when request is missing challenge - SignatureValidationError: raised when signature cannot be validated - """ - try: - if not body: - raise SignatureValidationError('Missing body') - - json_body = json.loads(body) - - encoded_signature = json_body.get('signature', '') - encoded_cipher = json_body.get('message', '') - if not encoded_signature: - raise SignatureValidationError('Missing signature') - if not encoded_cipher: - raise SignatureValidationError('Missing message') - - # Convert our encoded signature and body to bytes - encoded_cipher_bytes: bytes = encoded_cipher.encode("utf-8") - - # We sign the encoded cipher text so we decode our signature, but not our cipher text yet - decoded_sig_bytes: bytes = base64.b64decode(encoded_signature) - - try: - # Cryptography's verify method throws rather than returning false. - crypto.verify_signature(secret, encoded_cipher_bytes, decoded_sig_bytes) - except InvalidSignature as exc: - raise SignatureValidationError('Invalid signature') from exc - - # Now that we've verified our signature we decode our cipher to get the raw bytes - decrypted_body = crypto.decrypt(private_key, encoded_cipher) - - try: - request_json = json.loads(decrypted_body) - except json.JSONDecodeError as exc: - raise RequestValidationError('Invalid request data') from exc - - challenge = request_json.get('challenge') - if not challenge: - raise ServerChallengeValidationError('Missing challenge') - - except RequestValidationError: - raise - except Exception as exc: - logger.exception('Unexpected error validating request') - raise RequestValidationError('Cannot validate request') from exc - - return request_json, challenge - - -def make_request( - secret, - public_key, - text, - url='http://0.0.0.0:7150/parse', - context=None, - params=None, - frame=None, - history=None, -): - challenge = os.urandom(64).hex() - - context = context or { - 'orgId': 'fake-org-id', - 'userId': 'fake-user-id', - 'userType': 'fake', - 'supportedDirectives': ['reply', 'speak', 'display-web-view', 'sleep', 'listen'], - } - - request = { - k: v - for k, v in { - 'challenge': challenge, - 'text': text, - 'context': context, - 'params': params, - 'frame': frame, - 'history': history, - }.items() - if v is not None - } - - payload = crypto.prepare_payload(json.dumps(request), public_key, secret) - - res = requests.post(url, json=payload) - - if res.status_code != 200: - raise ResponseValidationError('Request failed') - - response_body = res.json() - - if response_body.get('challenge') != challenge: - raise ClientChallengeValidationError('Response failed challenge') - - return response_body - - -def make_health_check(secret, public_key, url='http://0.0.0.0:7150/parse'): - challenge = os.urandom(32).hex() - token = crypto.generate_token(challenge, public_key) - signature = crypto.sign_token(token, secret) - - query_params = { - 'signature': signature, - 'message': token - } - res = requests.get(url, params=query_params) - - if res.status_code != 200: - raise ResponseValidationError('Health check failed') - - json_resp = res.json() - - if json_resp.pop('challenge', None) != challenge: - raise ClientChallengeValidationError('Response failed challenge') - - return json_resp diff --git a/webex_assistant_sdk/server.py b/webex_assistant_sdk/server.py deleted file mode 100644 index 68229e5..0000000 --- a/webex_assistant_sdk/server.py +++ /dev/null @@ -1,134 +0,0 @@ -import base64 -import json -import logging -import os -import time -import uuid - -from cryptography.exceptions import InvalidSignature -from flask import Flask, jsonify, request -from flask_cors import CORS -from mindmeld import DialogueResponder -from mindmeld.app_manager import ApplicationManager -from mindmeld.exceptions import BadMindMeldRequestError -from mindmeld.server import MindMeldRequest - -from . import crypto -from ._version import api_version -from .exceptions import ( - RequestValidationError, - ServerChallengeValidationError, - SignatureValidationError, -) -from .helpers import validate_request - -logger = logging.getLogger(__name__) - - -def create_skill_server( - app_manager: ApplicationManager, - secret: str, - private_key: str) -> Flask: - server = Flask('mindmeld') - CORS(server) - - server.request_class = MindMeldRequest - server._secret = secret - server._private_key = private_key - - # pylint: disable=unused-variable - @server.route('/parse', methods=['POST']) - def parse(): - """The main endpoint for the skill API""" - - start_time = time.time() - try: - use_encryption = not os.environ.get('WXA_SKILL_DEBUG', False) - if use_encryption: - request_json, challenge = validate_request( - secret, private_key, request.get_data().decode('utf-8') - ) - else: - request_json = json.loads(request.data) - challenge = None - except SignatureValidationError as exc: - raise BadMindMeldRequestError(exc.args[0], status_code=403) from exc - except (RequestValidationError, ServerChallengeValidationError) as exc: - raise BadMindMeldRequestError(exc.args[0], status_code=400) from exc - - safe_request = {} - for key in ['text', 'params', 'context', 'frame', 'history', 'verbose']: - if key in request_json: - safe_request[key] = request_json[key] - - response = app_manager.parse(**safe_request) - try: - res = DialogueResponder.to_json(response) - except AttributeError: - res = dict(response) - # add request id to response - # use the passed in id if any - request_id = request_json.get('request_id', str(uuid.uuid4())) - res.update( - { - 'request_id': request_id, - 'response_time': time.time() - start_time, - 'challenge': challenge, - } - ) - return res - - @server.route('/parse', methods=['GET']) - def health_check(): - # Our signature and cipher bytes are expected to be base64 encoded byte strings - encoded_signature: str = request.args.get("signature") - encoded_cipher: str = request.args.get("message") - - # Bail on missing signature - if not encoded_signature: - return jsonify({"error": "Missing signature"}, 400) - - # And on a missing message - if not encoded_cipher: - return jsonify({"error": "Missing message"}, 400) - - # Convert our encoded signature and body to bytes - encoded_cipher_bytes: bytes = encoded_cipher.encode("utf-8") - - # We sign the encoded cipher text so we decode our signature, but not our cipher text yet - decoded_sig_bytes: bytes = base64.b64decode(encoded_signature) - - try: - # Cryptography's verify method throws rather than returning false. - crypto.verify_signature(secret, encoded_cipher_bytes, decoded_sig_bytes) - except InvalidSignature: - return jsonify({"error": "Invalid signature"}, 400) - - # Now that we've verified our signature we decode our cipher to get the raw bytes - decrypted_challenge = crypto.decrypt(private_key, encoded_cipher) - - return jsonify( - { - 'challenge': decrypted_challenge, - 'status': 'OK', - 'api_version': '.'.join((str(i) for i in api_version)) - } - ) - - # handle exceptions - @server.errorhandler(BadMindMeldRequestError) - def handle_bad_request(error): - response = jsonify(error.to_dict()) - response.status_code = error.status_code - logger.error(json.dumps(error.to_dict())) - return response - - @server.errorhandler(500) - def handle_server_error(error): - response_data = {'error': error.message} - response = jsonify(response_data) - response.status_code = 500 - logger.error(json.dumps(response_data)) - return response - - return server diff --git a/webex_assistant_sdk/templates/mindmeld_template/cookiecutter.json b/webex_assistant_sdk/templates/mindmeld_template/cookiecutter.json deleted file mode 100644 index 008fb78..0000000 --- a/webex_assistant_sdk/templates/mindmeld_template/cookiecutter.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "skill_name": "mindmeld_skill", - "rsa_file_name": "mm_skill.id_rsa.pem", - "rsa_password": "", - "app_secret": "" -} \ No newline at end of file diff --git a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/docker-compose.yml b/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/docker-compose.yml deleted file mode 100644 index 2743cae..0000000 --- a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: "3" -services: - elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:6.4.2 - labels: - name: elasticsearch - environment: - - discovery.type=single-node - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - ulimits: - memlock: - soft: -1 - hard: -1 - volumes: - - data:/usr/share/elasticsearch/data - ports: - - 9200:9200 - - 9300:9300 - -volumes: - data: - driver: local \ No newline at end of file diff --git a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/requirements.txt b/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/requirements.txt deleted file mode 100644 index e278057..0000000 --- a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -mindmeld==4.3.4rc3 -webex-assistant-sdk==0.2.0 \ No newline at end of file diff --git a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__init__.py b/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__init__.py deleted file mode 100644 index 91e3f48..0000000 --- a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -from {{cookiecutter.skill_name}}.root import app - -__all__ = ['app'] diff --git a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__main__.py b/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__main__.py deleted file mode 100644 index 03b4f61..0000000 --- a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/__main__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module defines the command line interface for this app. Use -`python -m ` to see available commands. -""" - -if __name__ == '__main__': - from . import app - app.cli() diff --git a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/root.py b/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/root.py deleted file mode 100644 index 94ae891..0000000 --- a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/root.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module contains an empty application container. -It is defined here to avoid circular imports -""" -from pathlib import Path - -from webex_assistant_sdk.app import SkillApplication -from webex_assistant_sdk.crypto import load_private_key_from_file - -secret = '{{cookiecutter.app_secret}}' - -key = load_private_key_from_file(str(Path(__file__).resolve().parent / 'id_rsa.pem')) -app = SkillApplication(__name__, secret=secret, private_key=key) - - -@app.introduce() -@app.handle(intent='greet') -def greet(request, responder): - del request - responder.reply('Hi, I am {{cookiecutter.skill_name}}!') - - -@app.handle(intent='exit') -def exit_(request, responder): - del request - responder.reply('Bye!') - - -@app.middleware -def add_sleep(request, responder, handler): - handler(request, responder) - # ensure response ends with `listen` or `sleep` - if responder.directives[-1]['name'] not in {'listen', 'sleep'}: - responder.sleep() - - -__all__ = ['app'] diff --git a/get_started_documentation/echo-skill-secure/echo_skill_secure/__init__.py b/webex_skills/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from get_started_documentation/echo-skill-secure/echo_skill_secure/__init__.py rename to webex_skills/__init__.py diff --git a/webex_skills/api/__init__.py b/webex_skills/api/__init__.py new file mode 100644 index 0000000..be5f1dd --- /dev/null +++ b/webex_skills/api/__init__.py @@ -0,0 +1,3 @@ +from .middlewares import DecryptionMiddleware +from .mindmeld import MindmeldAPI +from .simple import SimpleAPI diff --git a/webex_skills/api/base.py b/webex_skills/api/base.py new file mode 100644 index 0000000..38f6a1f --- /dev/null +++ b/webex_skills/api/base.py @@ -0,0 +1,58 @@ +import base64 +from typing import Optional + +from fastapi import FastAPI +from pydantic import BaseSettings +from starlette.middleware import Middleware +from starlette.responses import JSONResponse + +from ..crypto import verify_signature +from ..crypto.messages import decrypt, load_private_key +from ..models.http import SkillInvokeRequest, SkillInvokeResponse +from ..settings import SkillSettings +from .middlewares import DecryptionMiddleware +from .middlewares.signing import SignatureMiddleware + + +class BaseAPI(FastAPI): + def __init__(self, nlp=None, dialogue_manager=None, settings: Optional[BaseSettings] = None, **kwargs): + self.settings = settings or SkillSettings() + self.private_key = None + self.secret = None + self.nlp = nlp + self.dialogue_manager = dialogue_manager + + middleware = kwargs.pop('middlewares', []) + + if self.settings.use_encryption: + self.private_key = load_private_key(self.settings.private_key_path.read_bytes()) + self.secret = self.settings.secret.encode('utf-8') + # NOTE: The order here is significant. We sign the encrypted message so we want our signature + # verification to run _before_ the decryption middleware which will alter the message to the + # decrypted version. + middleware = [ + Middleware(SignatureMiddleware, secret=self.secret), + Middleware(DecryptionMiddleware, private_key=self.private_key), + *middleware, + ] + + super().__init__(**kwargs, middleware=middleware) + self.router.add_api_route('/parse', self.parse, methods=['POST'], response_model=SkillInvokeResponse) + self.router.add_api_route('/check', self.check, methods=['GET']) + + async def parse(self, request: SkillInvokeRequest): + pass + + # This essentially only works because there's no body and thus no call to receive() + # we should revisit this after seeing if the middlewares can be replaced with custom request types + async def check(self, signature: str, message: str): + message_bytes = message.encode('utf-8') + signature: bytes = base64.b64decode(signature.encode('utf-8')) + + if not verify_signature(secret=self.secret, message=message_bytes, signature=signature): + return JSONResponse({'message': 'Failed to verify signature'}, status_code=400) + try: + challenge = decrypt(private_key=self.private_key, message=message_bytes) + except ValueError: + return JSONResponse({'message': 'Failed to decrypt message'}, status_code=400) + return {'challenge': challenge} diff --git a/webex_skills/api/middlewares/__init__.py b/webex_skills/api/middlewares/__init__.py new file mode 100644 index 0000000..c7b99f9 --- /dev/null +++ b/webex_skills/api/middlewares/__init__.py @@ -0,0 +1 @@ +from .decryption import DecryptionMiddleware diff --git a/webex_skills/api/middlewares/base.py b/webex_skills/api/middlewares/base.py new file mode 100644 index 0000000..7ca5238 --- /dev/null +++ b/webex_skills/api/middlewares/base.py @@ -0,0 +1,28 @@ +import typing + +from starlette.requests import ClientDisconnect +from starlette.types import ASGIApp, Message, Receive, Send + + +class BaseReceiver: + def __init__(self, app: ASGIApp, receive: Receive, send: Send) -> None: + self.app = app + self.receive = receive + self.send = send + + async def stream_body(self, message: Message) -> typing.AsyncGenerator[bytes, None]: + while True: + if message["type"] == "http.disconnect": + raise ClientDisconnect() + body = message.get("body", b"") + if body: + yield body + if not message.get("more_body", False): + break + + message = await self.receive() + yield b"" + + async def message_body(self, message: Message) -> bytes: + chunks = [chunk async for chunk in self.stream_body(message)] + return b"".join(chunks) diff --git a/webex_skills/api/middlewares/decryption.py b/webex_skills/api/middlewares/decryption.py new file mode 100644 index 0000000..ad5f76b --- /dev/null +++ b/webex_skills/api/middlewares/decryption.py @@ -0,0 +1,55 @@ +import json +import logging + +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from starlette.types import ASGIApp, Message, Receive, Scope, Send + +from ...crypto import decrypt +from .base import BaseReceiver + +logger = logging.getLogger(__name__) + + +class DecryptionError(Exception): + pass + + +class DecryptionMiddleware: + def __init__(self, app: ASGIApp, private_key: RSAPrivateKey): + self.app = app + self.private_key = private_key + + async def __call__(self, scope: Scope, receive: Receive, send: Send): + if scope['type'] != 'http': + await self.app(scope, receive, send) + return + + receiver = DecryptingReceiver(self.private_key, self.app, receive, send) + response = receiver(scope, receive, send) + await response + + +class DecryptingReceiver(BaseReceiver): + def __init__(self, private_key: RSAPrivateKey, app: ASGIApp, receive: Receive, send: Send): + super().__init__(app=app, receive=receive, send=send) + self.private_key = private_key + + async def __call__(self, scope, receive: Receive, send: Send): + await self.app(scope, self.receive_decrypted, send) + + async def receive_decrypted(self) -> Message: + message = await self.receive() + + assert message["type"] == "http.request" + message_body = await self.message_body(message) + encrypted_message = json.loads(message_body) + try: + message["body"] = decrypt(self.private_key, encrypted_message['message'].encode('utf-8')) + except ValueError: + # We log with error rather than exception here because the exception raised by cryptography + # if decryption fails doesn't provide useful information, and the stack trace is largely unhelpful + msg = 'Failed to decrypt payload' + logger.error(msg) + raise DecryptionError(msg) + + return message diff --git a/webex_skills/api/middlewares/signing.py b/webex_skills/api/middlewares/signing.py new file mode 100644 index 0000000..10bfdff --- /dev/null +++ b/webex_skills/api/middlewares/signing.py @@ -0,0 +1,55 @@ +import base64 +import json +import logging + +from starlette.types import ASGIApp, Message, Receive, Scope, Send + +from ...crypto import verify_signature +from .base import BaseReceiver + +logger = logging.getLogger(__name__) + + +class SignatureVerificationError(Exception): + pass + + +class SignatureMiddleware: + def __init__(self, app: ASGIApp, secret: bytes): + self.app = app + self.secret = secret + + async def __call__(self, scope: Scope, receive: Receive, send: Send): + if scope['type'] != 'http': + await self.app(scope, receive, send) + return + + receiver = SignatureReceiver(self.secret, self.app, receive, send) + response = receiver(scope, receive, send) + await response + + +class SignatureReceiver(BaseReceiver): + def __init__(self, secret: bytes, app: ASGIApp, receive: Receive, send: Send): + super().__init__(app, receive, send) + self.secret = secret + + async def __call__(self, scope, receive: Receive, send: Send): + await self.app(scope, self.verify_signature, send) + + async def verify_signature(self) -> Message: + message = await self.receive() + + assert message["type"] == "http.request" + message_body = await self.message_body(message) + encrypted_body = json.loads(message_body) + encrypted_message = encrypted_body['message'] + signature = encrypted_body['signature'] + + signature: bytes = base64.b64decode(signature) + + if not verify_signature(self.secret, encrypted_message.encode('utf-8'), signature): + msg = 'Failed to validate signature' + logger.error(msg) + raise SignatureVerificationError(msg) + return message diff --git a/webex_skills/api/mindmeld.py b/webex_skills/api/mindmeld.py new file mode 100644 index 0000000..9cde155 --- /dev/null +++ b/webex_skills/api/mindmeld.py @@ -0,0 +1,47 @@ +from typing import cast + +from ..dialogue.manager import MMDialogueManager +from ..models.http import SkillInvokeRequest, SkillInvokeResponse +from ..models.mindmeld import DialogueState, ProcessedQuery +from ..supress_warnings import suppress_warnings +from .base import BaseAPI + + +class MindmeldAPI(BaseAPI): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + if not self.nlp: + with suppress_warnings(): + from mindmeld import NaturalLanguageProcessor + + self.nlp = NaturalLanguageProcessor(self.settings.app_dir) + self.nlp.load() + + if not self.dialogue_manager: + self.dialogue_manager = MMDialogueManager() + + async def parse(self, request: SkillInvokeRequest): + """A default parse method for mindmeld apps so the user only has to handle dialogue stuff""" + + current_state = DialogueState(**request.dict()) + processed_query = self.nlp.process( + query_text=request.text, + locale=request.params.locale, + language=request.params.language, + time_zone=request.params.time_zone, + timestamp=request.params.timestamp, + dynamic_resource=request.params.dynamic_resource, + ) + + # Just here to make type checking happy because NLP.process incorrectly defines it's return type + processed_query = cast(dict, processed_query) + + processed_query = ProcessedQuery(**processed_query) + new_state = await self.dialogue_manager.handle(processed_query, current_state) + response = SkillInvokeResponse(**new_state.dict(), challenge=request.challenge) + return response + + def handle(self, *, domain=None, intent=None, entities=None, default=False): + """Wraps a function to behave as a dialogue handler""" + return self.dialogue_manager.add_rule(domain=domain, intent=intent, entities=entities, default=default) diff --git a/webex_skills/api/simple.py b/webex_skills/api/simple.py new file mode 100644 index 0000000..c217050 --- /dev/null +++ b/webex_skills/api/simple.py @@ -0,0 +1,23 @@ +from typing import Any, Optional + +from ..dialogue.manager import SimpleDialogueManager +from ..models.http import SkillInvokeRequest, SkillInvokeResponse +from ..models.mindmeld import DialogueState +from .base import BaseAPI + + +class SimpleAPI(BaseAPI): + def __init__(self, **extra: Any) -> None: + super().__init__(**extra) + if not self.dialogue_manager: + self.dialogue_manager = SimpleDialogueManager() + + async def parse(self, request: SkillInvokeRequest) -> SkillInvokeResponse: + current_state = DialogueState(**request.dict()) + new_state = await self.dialogue_manager.handle(query=current_state.text, current_state=current_state) + response = SkillInvokeResponse(**new_state.dict(), challenge=request.challenge) + return response + + def handle(self, *, pattern: Optional[str] = None, default=False, targeted_only=False): + """Wraps a function to behave as a dialogue handler""" + return self.dialogue_manager.add_rule(pattern=pattern, default=default, targeted_only=targeted_only) diff --git a/webex_skills/cli/__init__.py b/webex_skills/cli/__init__.py new file mode 100644 index 0000000..9990014 --- /dev/null +++ b/webex_skills/cli/__init__.py @@ -0,0 +1,23 @@ +import typer + +from .crypto import app as crypto_app +from .nlp import app as nlp_app +from .project import app as project_app +from .remote import remote as remote_app +from .skill import app as skill_app + +app = typer.Typer() + +app.add_typer(skill_app, name="skills") +app.add_typer(crypto_app, name="crypto") +app.add_typer(nlp_app, name='nlp') +app.add_typer(project_app) +app.add_typer(remote_app) + + +def main(): + app() + + +if __name__ == '__main__': + main() diff --git a/webex_skills/cli/config.py b/webex_skills/cli/config.py new file mode 100644 index 0000000..adae5e1 --- /dev/null +++ b/webex_skills/cli/config.py @@ -0,0 +1,27 @@ +import json +from pathlib import Path + +import typer + + +def get_skill_config(name=None): + app_dir = Path(typer.get_app_dir('skills-cli', force_posix=True)) + config_file = app_dir / 'config.json' + + if not app_dir.exists(): + typer.echo('Creating default configuration') + app_dir.mkdir(parents=True) + config = {} + else: + config = json.loads(config_file.read_text('utf-8')) or {} + + remotes = config.get('remotes', {}) + if not name: + return remotes + + remote_config = remotes.get(name) + if not remote_config: + typer.secho(f'No configured remote with the name {name} found', color=typer.colors.RED, err=True) + raise typer.Exit(1) + + return remote_config diff --git a/webex_skills/cli/crypto.py b/webex_skills/cli/crypto.py new file mode 100644 index 0000000..05f5f53 --- /dev/null +++ b/webex_skills/cli/crypto.py @@ -0,0 +1,40 @@ +from pathlib import Path +from typing import Optional + +from cryptography.hazmat.primitives import serialization +import typer +from webex_skills import crypto + +app = typer.Typer() + + +@app.command() +def generate_keys( + filepath: Optional[Path] = typer.Argument( + None, help="The path where to save the keys created. By default, they get created in the current directory." + ), + name: Optional[str] = typer.Option('id_rsa', help="The name to use for the keys created."), +): + """Generate an RSA keypair""" + if not filepath: + filepath = Path.cwd() + + typer.secho('🔐 Generating new RSA keypair...', fg=typer.colors.GREEN) + + encryption = serialization.NoEncryption() + + priv_path = filepath / f'{name}.pem' + pub_path = filepath / f'{name}.pub' + + if priv_path.exists() or pub_path.exists(): + confirmation = typer.confirm(f'File exists, would you like to overwrite the files at {priv_path}') + if not confirmation: + return + typer.echo(f'Writing files {priv_path} and {pub_path} to {filepath.absolute()}') + crypto.generate_keys(priv_path, pub_path, encryption=encryption) + + +@app.command() +def generate_secret(): + """Generate a secret token for signing requests""" + typer.echo(crypto.generate_secret()) diff --git a/webex_skills/cli/helpers.py b/webex_skills/cli/helpers.py new file mode 100644 index 0000000..7dc7e67 --- /dev/null +++ b/webex_skills/cli/helpers.py @@ -0,0 +1,11 @@ +from webex_skills.supress_warnings import suppress_warnings + + +def create_nlp(app_path): + try: + with suppress_warnings(): + from mindmeld.components.nlp import NaturalLanguageProcessor + except ImportError: + raise ImportError('You must install the extras package webex-assistant-sdk[mindmeld] to use NLP commmands') + nlp = NaturalLanguageProcessor(app_path=app_path) + return nlp diff --git a/webex_skills/cli/nlp.py b/webex_skills/cli/nlp.py new file mode 100644 index 0000000..3931e47 --- /dev/null +++ b/webex_skills/cli/nlp.py @@ -0,0 +1,51 @@ +from pprint import pformat +from typing import Optional + +import typer + +from .config import get_skill_config +from .helpers import create_nlp + +app = typer.Typer(help='Commands for working with NLP models') + + +# take name to find app path, otherwise default to cwd +@app.command() +def build( + name: Optional[str] = typer.Argument( + None, + help="The name of the skill to build.", + ), +): + """Build nlp models associated with this skill""" + app_dir = '.' + if name: + config = get_skill_config(name) + app_dir = config['app_dir'] + + nlp = create_nlp(app_dir) + nlp.build() + + +@app.command() +def process( + name: Optional[str] = typer.Argument( + None, + help="The name of the skill to send the query to.", + ), +): + """Run a query through NLP processing""" + app_dir = '.' + if name: + config = get_skill_config(name) + app_dir = config['app_dir'] + + nlp = create_nlp(app_dir) + nlp.load() + + typer.echo('Enter a query below (Ctl+C to exit)') + query = typer.prompt('>>', prompt_suffix=' ') + while True: + output = nlp.process(query) + typer.secho(pformat(output, indent=2, width=120), fg=typer.colors.GREEN) + query = typer.prompt('>>', prompt_suffix=' ') diff --git a/webex_skills/cli/project.py b/webex_skills/cli/project.py new file mode 100644 index 0000000..6a92c26 --- /dev/null +++ b/webex_skills/cli/project.py @@ -0,0 +1,141 @@ +import json +from pathlib import Path +import secrets +import shutil +from typing import Optional + +import typer + +from ..crypto import generate_keys +from .config import get_skill_config +from .helpers import create_nlp + +app = typer.Typer(name='project') + + +@app.command(name='init') +def init( + skill_name: str = typer.Argument(..., help="The name of the skill you want to create"), + skill_path: Path = typer.Option( + '.', + help='Directory in which to initialize a skill project', + dir_okay=True, + file_okay=False, + writable=True, + resolve_path=True, + ), + secret: Optional[str] = typer.Option( + None, help="A secret for encryption. If not provided, one will be" " generated automatically." + ), + mindmeld: Optional[bool] = typer.Option( + False, + help="If flag set, a MindMeld app will be created, otherwise " "it defaults to a simple app", + is_flag=True, + ), +): + """Create a new skill project from a template""" + + if not secret: + typer.secho('Generating secret...') + secret = secrets.token_urlsafe(16) + + # TODO: Pass static path down + typer.secho(f'Generating skill {skill_name} project at {skill_path/skill_name}...') + if mindmeld: + create_mm_project(skill_name, skill_path, secret) + return + create_simple_project(skill_name, skill_path, secret) + + +def create_simple_project(skill_name: str, output_dir: Path, secret: str) -> None: + _create_project(skill_name, output_dir, 'default_app.py', secret) + + +def create_mm_project(skill_name, output_dir, secret) -> None: + """Create a mindmeld based project""" + app_dir = _create_project(skill_name, output_dir, 'mm_app.py', secret=secret) + + # Create MM NLP directory structure + domains_dir = app_dir / 'domains' + entities_dir = app_dir / 'entities' + entities_dir.mkdir() + + static_path = Path(__file__).parent.parent / 'static' + # Copy sample domains into domain folder + shutil.copytree(static_path / 'default_domains', domains_dir) + + # Build models + typer.secho('Initializing natural language processor', fg=typer.colors.GREEN) + nlp = create_nlp(str(app_dir)) + typer.secho('Building NLP models', fg=typer.colors.GREEN) + nlp.build() + + typer.secho('Success!') + + +def _create_project(skill_name: str, output_dir: Path, app_file_name: str, secret: str): + output_dir = output_dir / skill_name + typer.echo(f'Creating project directory {output_dir}') + output_dir.mkdir(parents=True, exist_ok=True) + + # Generate keys + typer.secho('🔐 Generating new RSA keypair...', fg=typer.colors.GREEN) + priv_path = output_dir / 'id_rsa.pem' + pub_path = output_dir / 'id_rsa.pub' + generate_keys(priv_path, pub_path) + + # Create directory structure + package_path = output_dir / skill_name.replace('-', '_') + package_path.mkdir() + + # Add __init__.py + init_path = package_path / '__init__.py' + init_path.touch() + + # Add tests directory + test_path = package_path / 'tests' + test_path.mkdir() + + static_path = Path(__file__).parent.parent / 'static' + + # Create pyproject.toml + toml_template = static_path / 'pyproject.toml.tmpl' + toml_content = toml_template.read_text() + + toml_content = toml_content.format(skill_name=skill_name) + toml_out_path = output_dir / 'pyproject.toml' + toml_out_path.write_text(toml_content) + + # Copy appropriate app file + app_file_path = static_path / app_file_name + app_file_dest = package_path / 'main.py' + shutil.copy(app_file_path, app_file_dest) + + app_dir = package_path.absolute() + # Create env file with default values + + env_template = static_path / 'env.tmpl' + env_content = env_template.read_text() + env_content = env_content.format( + skill_name=skill_name, skill_secret=secret, app_dir=app_dir, private_key_path=priv_path + ) + + env_file_path = output_dir / '.env' + env_file_path.write_text(env_content) + + remotes = get_skill_config() + remotes[skill_name] = { + 'name': skill_name, + 'url': "http://localhost:8080/parse", + 'secret': secret, + 'public_key_path': str(pub_path.absolute()), + 'private_key_path': str(priv_path.absolute()), + 'project_path': str(output_dir), + 'app_dir': str(app_dir), + } + + config_dir = Path(typer.get_app_dir('skills-cli', force_posix=True)) + config_file = config_dir / 'config.json' + + config_file.write_text(json.dumps({'remotes': remotes}, indent=2)) + return app_dir diff --git a/webex_skills/cli/remote.py b/webex_skills/cli/remote.py new file mode 100644 index 0000000..fc4a80d --- /dev/null +++ b/webex_skills/cli/remote.py @@ -0,0 +1,96 @@ +import json +from pathlib import Path +from pprint import pformat +from typing import Optional + +import typer + +from .config import get_skill_config + +remote = typer.Typer(name='remote', help='Commands for interacting with running skills') + + +def prompt_for_secret(): + secret: str = typer.prompt('Secret', hide_input=True) + while not len(secret) > 20: + typer.echo('Secrets must be at least 20 characters') + secret = typer.prompt('Secret', hide_input=True) + return secret + + +# TODO: Check that public key is actually a public key in the supported format +def prompt_for_key(): + public_key_path = Path(typer.prompt('Public key path', Path('./id_rsa.pub'))) + if public_key_path.exists(): + return public_key_path + + while True: + typer.secho(f'The path {public_key_path} does not exist, please try again', color=typer.colors.RED) + public_key_path = Path(typer.prompt('Public key path', public_key_path)) + if public_key_path.exists(): + return public_key_path + + +@remote.command() +def create( + name: str = typer.Argument(..., help="The name to give to the remote."), + url: Optional[str] = typer.Option(None, '-u', help="URL of the remote. If not provided it will be requested."), + secret: Optional[str] = typer.Option( + None, '--secret', '-s', help="The skill secret. If not provided it will be requested." + ), + public_key_path: Optional[Path] = typer.Option( + None, '-k', '--key', help="The path to the public key. If not provided it will be requested." + ), +): + """Add configuration for a new remote skill to the cli config file""" + app_dir = Path(typer.get_app_dir('skills-cli', force_posix=True)) + config_file = app_dir / 'config.json' + + if not app_dir.exists(): + app_dir.mkdir(parents=True) + + if config_file.exists(): + config = json.loads(config_file.read_text('utf-8')) + else: + typer.secho(f'Config file {config_file} not found, creating...') + config_file.touch() + config = {} + + remotes = config.get('remotes', {}) + existing_config = remotes.get(name, {}) + if existing_config: + typer.confirm( + f'A configuration with the name "{name}" already exists, would you like to overwrite it?', abort=True + ) + + if not secret: + secret = prompt_for_secret() + + if not public_key_path: + public_key_path = prompt_for_key() + + if not url: + url = typer.prompt('URL to invoke the skill', default='http://localhost:8080/parse') + + remotes[name] = {'name': name, 'url': url, 'secret': secret, 'public_key_path': str(public_key_path.absolute())} + config['remotes'] = remotes + config_file.write_text(json.dumps(config, indent=2)) + + +@remote.command('list') +def ls(name: Optional[str] = typer.Option(None, help="The name of a particular skill to display.")): + """List configured remote skills""" + remotes = get_skill_config() + if not remotes: + typer.secho('No configured remotes found', color=typer.colors.RED, err=True) + raise typer.Exit(1) + + if name: + remote_config = remotes.get(name) + if not remote_config: + typer.secho(f'No configured remote with the name {name} found', color=typer.colors.RED, err=True) + raise typer.Exit(1) + else: + remote_config = remotes + + typer.echo(pformat(remote_config)) diff --git a/webex_skills/cli/skill.py b/webex_skills/cli/skill.py new file mode 100644 index 0000000..d1701ee --- /dev/null +++ b/webex_skills/cli/skill.py @@ -0,0 +1,164 @@ +from datetime import datetime +import json +from json import JSONDecodeError +import locale +import os +from pathlib import Path +from pprint import pformat +import sys +from typing import Optional + +import requests +import typer +from typer import colors +import uvicorn + +from ..crypto.messages import generate_token, prepare_payload +from ..crypto.signatures import sign_token +from .config import get_skill_config + +app = typer.Typer() + + +@app.command() +def invoke( + name: Optional[str] = typer.Argument( + None, + help="The name of the skill to invoke. If none specified, you would need to" + " at least provide the `public_key_path` and `secret`. If specified, all" + " following configuration (keys, secret, url, ect.) will be extracted" + " from the skill.", + ), + secret: Optional[str] = typer.Option( + None, '--secret', '-s', help="The secret for the skill. If none provided you will be asked for it." + ), + public_key_path: Optional[Path] = typer.Option( + None, '-k', '--key', help="The path of the public key for the skill." + ), + url: Optional[str] = typer.Option(None, '-u', help="The public url for the skill."), + verbose: Optional[bool] = typer.Option(None, '-v', help="Set this flag to get a more verbose output."), + encrypted: Optional[bool] = typer.Option( + True, '--encrypt/--no-encrypt', is_flag=True, help="Flag to specify if the skill is using encryption." + ), +): + """Invoke a skill running locally or remotely""" + if name: + # Load details from config + config = get_skill_config(name) + url = url or config['url'] + public_key_path = public_key_path or Path(config['public_key_path']) + secret = secret or config['secret'] + + public_key_text = public_key_path.read_text(encoding='utf-8') + typer.echo('Enter commands below (Ctl+C to exit)') + query = typer.prompt('>>', prompt_suffix=' ') + invoke_skill(query, url, encrypted, public_key_text, secret, verbose) + + +def invoke_skill(query, url, encrypted, public_key, secret, verbose=False): + challenge = os.urandom(32).hex() + default_params = { + 'time_zone': 'UTC', + 'timestamp': datetime.utcnow().timestamp(), + 'language': 'en', + } + message = { + 'challenge': challenge, + 'text': query, + 'context': {}, + 'params': default_params, + 'frame': {}, + 'history': [], + } + + while True: + req = message + if encrypted: + req = prepare_payload(json.dumps(message), public_key, secret) + + resp = requests.post(url, json=req) + + if verbose: + typer.secho(f'Sending Message: {pformat(req)}\n\n', fg=typer.colors.GREEN) + + if resp.status_code != 200: + typer.secho(f'Skill responded with status code {resp.status_code}', fg=typer.colors.RED) + + try: + json_resp = resp.json() + except JSONDecodeError: + typer.secho('Unable to deserialize JSON response') + json_resp = {} + + if not json_resp.get('challenge') == challenge: + typer.secho('Skill did not respond with expected challenge value', fg=typer.colors.RED, err=True) + + typer.secho(pformat(json_resp, indent=2, width=120), fg=typer.colors.GREEN) + query = typer.prompt('>>', prompt_suffix=' ') + + challenge = os.urandom(32).hex() + message = { + 'challenge': challenge, + 'text': query, + 'context': json_resp.get('context', {}), + 'params': json_resp.get('params', default_params), + 'frame': json_resp.get('frame', []), + 'history': json_resp.get('history', []), + } + + +@app.command() +def run(skill_name: str = typer.Argument(..., help="The name of the skill to run.")): + config = get_skill_config(skill_name) + sys.path.insert(0, config['project_path']) + os.environ['SKILLS_PRIVATE_KEY_PATH'] = config['private_key_path'] + os.environ['SKILLS_SECRET'] = config['secret'] + os.environ['SKILLS_APP_DIR'] = config['app_dir'] + uvicorn.run(f'{skill_name}.main:api', host="127.0.0.1", port=8080, log_level="info") + + +@app.command() +def check( + name: Optional[str] = typer.Argument( + None, + help="The name of the skill to check. If none specified, you would need to" + " at least provide the `public_key_path` and `secret`. If specified, all" + " following configuration (keys, secret, etc.) will be extracted" + " from the skill.", + ), + secret: Optional[str] = typer.Option( + None, '--secret', '-s', help="The secret for the skill. If none provided you will be asked for it." + ), + public_key_path: Optional[Path] = typer.Option( + None, '-k', '--key', help="The path of the public key for the skill." + ), + url: Optional[str] = typer.Option('http://localhost:8080/check', '-u', help="The check url for the skill."), +): + if name: + # Load details from config + config = get_skill_config(name) + public_key_path = public_key_path or Path(config['public_key_path']) + secret = secret or config['secret'] + + public_key_text = public_key_path.read_text(encoding='utf-8') + challenge = os.urandom(32).hex() + token = generate_token(challenge, public_key_text) + signature = sign_token(token, secret) + + resp = requests.get(url, params={'signature': signature, 'message': token}) + if resp.status_code != 200: + typer.secho(f'Non-200 response from skill: {resp.content}', fg=colors.RED) + return + + try: + json_resp = resp.json() + except JSONDecodeError: + typer.secho(f'Invalid json response {resp.content}', fg=colors.RED) + return + + resp_challenge = json_resp.get('challenge') + if not resp_challenge or resp_challenge != challenge: + typer.secho(f'Invalid challenge response {resp_challenge}', fg=colors.RED) + return + + typer.secho(f'{name} appears to be working correctly', fg=colors.GREEN) diff --git a/webex_skills/crypto/__init__.py b/webex_skills/crypto/__init__.py new file mode 100644 index 0000000..af277b3 --- /dev/null +++ b/webex_skills/crypto/__init__.py @@ -0,0 +1,5 @@ +from .generation import generate_keys, generate_secret +from .messages import decrypt, prepare_payload +from .signatures import sign_token, verify_signature + +__all__ = ['generate_keys', 'generate_secret', 'prepare_payload', 'decrypt', 'verify_signature', 'sign_token'] diff --git a/webex_skills/crypto/generation.py b/webex_skills/crypto/generation.py new file mode 100644 index 0000000..00dd6be --- /dev/null +++ b/webex_skills/crypto/generation.py @@ -0,0 +1,35 @@ +from pathlib import Path +import secrets +from typing import Optional, Union + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, NoEncryption + +EncryptionTypes = Union[BestAvailableEncryption, NoEncryption] + + +def generate_keys(priv_path: Path, pub_path: Path, encryption: Optional[EncryptionTypes] = None): + + if not encryption: + encryption = NoEncryption() + private_key = rsa.generate_private_key(65537, 4096) + public_key: rsa.RSAPublicKey = private_key.public_key() + + priv_path.write_bytes( + private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=encryption, + ) + ) + + pub_path.write_bytes( + public_key.public_bytes( + encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + ) + + +def generate_secret(): + return secrets.token_urlsafe(16) diff --git a/get_started_documentation/echo-skill-secure/echo_skill_secure_tester/main.py b/webex_skills/crypto/messages.py similarity index 57% rename from get_started_documentation/echo-skill-secure/echo_skill_secure_tester/main.py rename to webex_skills/crypto/messages.py index 4c94800..abc891c 100644 --- a/get_started_documentation/echo-skill-secure/echo_skill_secure_tester/main.py +++ b/webex_skills/crypto/messages.py @@ -1,17 +1,16 @@ -import asyncio import base64 -import json -import os +import binascii from typing import cast -import aiohttp +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.fernet import Fernet -from cryptography.hazmat.primitives import hashes, hmac, serialization +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric.padding import MGF1, OAEP -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey +from cryptography.hazmat.primitives.serialization import load_pem_private_key -PUBLIC_KEY = os.getenv('ECHO_PUBLIC_KEY') -SECRET = os.getenv('ECHO_SECRET') +from ..exceptions import EncryptionKeyError +from .signatures import sign_token def encrypt_fernet_key(fernet_key: bytes, pub_key: bytes) -> bytes: @@ -40,7 +39,8 @@ def generate_token(message: str, pub_key: str) -> str: encoded_fernet_key = base64.b64encode(encrypted_fernet_key).decode('utf-8') encoded_message = base64.b64encode(encrypted_message).decode('utf-8') - # Our final token format is a string of base64 bytes representing our key/message, delineated by a '.' + # Our final token format is a string of base64 bytes representing + # our key/message, delineated by a '.' return f'{encoded_fernet_key}.{encoded_message}' @@ -54,38 +54,24 @@ def prepare_payload(message: str, pub_key: str, secret: str) -> dict: } -def sign_token(message: str, secret: str) -> str: - secret_bytes = secret.encode('utf-8') - message_bytes = message.encode('utf-8') - sig = hmac.HMAC(secret_bytes, hashes.SHA256()) - sig.update(message_bytes) - sig_bytes = sig.finalize() - return base64.b64encode(sig_bytes).decode('utf-8') - +def decrypt(private_key: RSAPrivateKey, message: bytes) -> bytes: -challenge = os.urandom(32).hex() -message_body = { - "text": ["Hello world!"], - "context": {}, - "params": {}, - "frame": {}, - "history": {}, - "challenge": challenge, -} - -request = prepare_payload(json.dumps(message_body), PUBLIC_KEY, SECRET) + padding = OAEP(mgf=MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) + encrypted_fernet_key, fernet_token = message.split(b".") + encrypted_fernet_key_bytes = base64.b64decode(encrypted_fernet_key) -async def main(): - async with aiohttp.ClientSession() as session: - async with session.post('http://0.0.0.0:8080/', json=request) as resp: - print(f'Status: {resp.status}') - response = await resp.json() - pretty_response = json.dumps(response, indent=4) - print(f'Response:\n{pretty_response}') + fernet_key = private_key.decrypt(encrypted_fernet_key_bytes, padding) - assert challenge == response['challenge'] + fernet_token_bytes = base64.b64decode(fernet_token) + payload = Fernet(fernet_key).decrypt(fernet_token_bytes) + return payload -loop = asyncio.get_event_loop() -loop.run_until_complete(main()) +def load_private_key(private_key_bytes: bytes): + """Loads a private key in PEM format""" + try: + private_key: RSAPrivateKey = load_pem_private_key(private_key_bytes, None) + return private_key + except (binascii.Error, ValueError, UnsupportedAlgorithm) as ex: + raise EncryptionKeyError('Unable to load private key') from ex diff --git a/webex_skills/crypto/signatures.py b/webex_skills/crypto/signatures.py new file mode 100644 index 0000000..7539db9 --- /dev/null +++ b/webex_skills/crypto/signatures.py @@ -0,0 +1,23 @@ +import base64 + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes, hmac + + +def verify_signature(secret: bytes, message: bytes, signature: bytes) -> bool: + sig = hmac.HMAC(secret, hashes.SHA256()) + sig.update(message) + try: + sig.verify(signature) + return True + except InvalidSignature: + return False + + +def sign_token(message: str, secret: str) -> str: + secret_bytes = secret.encode('utf-8') + message_bytes = message.encode('utf-8') + sig = hmac.HMAC(secret_bytes, hashes.SHA256()) + sig.update(message_bytes) + sig_bytes = sig.finalize() + return base64.b64encode(sig_bytes).decode('utf-8') diff --git a/get_started_documentation/echo-skill/echo_skill/__init__.py b/webex_skills/dialogue/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from get_started_documentation/echo-skill/echo_skill/__init__.py rename to webex_skills/dialogue/__init__.py diff --git a/webex_skills/dialogue/manager.py b/webex_skills/dialogue/manager.py new file mode 100644 index 0000000..734e263 --- /dev/null +++ b/webex_skills/dialogue/manager.py @@ -0,0 +1,100 @@ +import inspect +import re +from typing import List, Optional + +from ..models.mindmeld import DialogueState +from ..types import DialogueHandler, DialogueQuery, RuleMap +from .rules import MMDialogueStateRule, SimpleDialogueStateRule + + +class MissingHandler(Exception): + pass + + +class DialogueManager: + def __init__(self, rules: Optional[RuleMap] = None, default_handler: Optional[DialogueHandler] = None): + self.rules = rules or {} + self.default_handler = default_handler + + def get_handler(self, query: DialogueQuery, target_state: Optional[str] = None) -> Optional[DialogueHandler]: + for rule, handler in self.rules.items(): + if target_state and rule.dialogue_state == target_state: + return handler + if not target_state and rule.match(query): + return handler + return None + + async def handle(self, query: DialogueQuery, current_state: DialogueState): + # Iterate over our rules, taking the first match + handler = self.get_handler(query, current_state.params.target_dialogue_state) + handler = handler or self.default_handler + + if not handler: + # TODO: Different message + raise MissingHandler('No handler found') + + # TODO: Use annotated types rather than length + handler_args = inspect.signature(handler) + if len(handler_args.parameters) == 2: + new_state = await handler(current_state, query) + else: + new_state = await handler(current_state) + new_state.update_history(old_state=current_state) + return new_state + + +class MMDialogueManager(DialogueManager): + def add_rule( + self, + *, + name: Optional[str] = None, + default=False, + domain=None, + intent=None, + entities=None, + targeted_only=False, + ): + """Wraps a function to behave as a dialogue handler""" + + def decorator(handler: DialogueHandler) -> DialogueHandler: + if default: + self.default_handler = handler + return handler + + rule_name = name or handler.__name__.lower() + skill_rule = MMDialogueStateRule(domain, intent, entities, rule_name, targeted_only=targeted_only) + self.rules[skill_rule] = handler + existing_rules: List[(MMDialogueStateRule, DialogueHandler)] = list(self.rules.items()) + self.rules = dict(sorted(existing_rules, reverse=True)) + + return handler + + return decorator + + +class SimpleDialogueManager(DialogueManager): + def add_rule( + self, *, name: Optional[str] = None, pattern: Optional[str] = None, default=False, targeted_only=False + ): + """Wraps a function to behave as a dialogue handler""" + + # TODO: Take a closer look at the setup here, I'd like to have type hinting + # catch if a function doesn't meet what's expected as a DialogueHandler + # Just checking if handler is a coroutine is also an option + def decorator(handler: DialogueHandler) -> DialogueHandler: + if default: + self.default_handler = handler + return handler + + rule_name = name or handler.__name__.lower() + if targeted_only: + skill_rule = SimpleDialogueStateRule(None, rule_name) + self.rules[skill_rule] = handler + else: + compiled = re.compile(pattern) + skill_rule = SimpleDialogueStateRule(compiled, rule_name) + self.rules[skill_rule] = handler + + return handler + + return decorator diff --git a/webex_skills/dialogue/responses.py b/webex_skills/dialogue/responses.py new file mode 100644 index 0000000..9a11f0e --- /dev/null +++ b/webex_skills/dialogue/responses.py @@ -0,0 +1,80 @@ +from typing import Any, Dict +from typing import List as _List +from typing import Optional, Union + +from pydantic import BaseModel + +PayloadDict = Dict[str, Any] +Payload = Union[PayloadDict, _List[Any]] + + +class SkillDirective(BaseModel): + name: str + type: str + payload: Optional[Payload] + + def dict(self, *args, exclude_none=True, **kwargs) -> PayloadDict: + return super().dict(*args, **kwargs, exclude_none=True) + + +class ViewDirective(SkillDirective): + type: str = 'view' + + +class ActionDirective(SkillDirective): + type: str = 'action' + + +class Listen(ActionDirective): + name: str = 'listen' + + +class Reply(ActionDirective): + name: str = 'reply' + + def __init__(self, text: str): + super().__init__(payload={'text': text}) + + +class Speak(ActionDirective): + name: str = 'speak' + + def __init__(self, text): + super().__init__(payload={'text': text}) + + +class Sleep(ActionDirective): + name: str = 'sleep' + payload: Dict[str, int] + + def __init__(self, delay: int = 0): + super().__init__(payload={'delay': delay}) + + +class DisplayWebView(ActionDirective): + name: str = 'display-web-view' + + def __init__(self, url: str, title: Optional[str]): + super().__init__(payload={'url': url, 'title': title}) + + +class ClearWebView(ActionDirective): + name: str = 'display-web-view' + + +class UIHint(ViewDirective): + name: str = 'ui-hint' + + def __init__(self, texts, prompt, display_immediately): + super().__init__(payload={'texts': texts, 'prompt': prompt, 'display_immediately': display_immediately}) + + +class AsrHint(ActionDirective): + name: str = 'asr-hint' + + def __init__(self, texts): + super().__init__(payload={'texts': texts}) + + +class AssistantEvent(ActionDirective): + name: str = 'assistant-event' diff --git a/webex_skills/dialogue/rules.py b/webex_skills/dialogue/rules.py new file mode 100644 index 0000000..4bc9bf1 --- /dev/null +++ b/webex_skills/dialogue/rules.py @@ -0,0 +1,63 @@ +from functools import total_ordering +import re +from typing import Optional + +from ..models.mindmeld import ProcessedQuery + + +@total_ordering +class MMDialogueStateRule: + def __init__(self, domain, intent, entities, dialogue_state, targeted_only): + self.domain = domain + self.intent = intent + + if entities: + entities = set(entities) + + self.entities = entities + self.targeted_only = targeted_only + self.dialogue_state = dialogue_state + + def match(self, processed_query: ProcessedQuery): + if self.targeted_only: + return False + + if self.domain is not None and self.domain != processed_query.domain: + return False + + # check intent is correct + if self.intent is not None and self.intent != processed_query.intent: + return False + + # check expected entity types are present + if self.entities is not None: + entity_types = set(entity['type'] for entity in processed_query.entities) + if len(self.entities & entity_types) < len(self.entities): + return False + + return True + + def __gt__(self, other: "MMDialogueStateRule"): + return self.specificity > other.specificity + + @property + def specificity(self): + value = 0 + if self.domain: + value += 1 + if self.intent: + value += 1 + if self.entities: + value += len(self.entities) + return value + + +class SimpleDialogueStateRule: + def __init__(self, regex: Optional[re.Pattern], dialogue_state: str): + self.regex = regex + self.dialogue_state = dialogue_state + + def match(self, text) -> Optional[re.Match]: + if not self.regex: + return None + return self.regex.match(text) diff --git a/webex_skills/exceptions.py b/webex_skills/exceptions.py new file mode 100644 index 0000000..17a6f02 --- /dev/null +++ b/webex_skills/exceptions.py @@ -0,0 +1,6 @@ +class WebexAssistantSDKException(Exception): + pass + + +class EncryptionKeyError(WebexAssistantSDKException): + pass diff --git a/tests/skill/config.py b/webex_skills/logging.py similarity index 100% rename from tests/skill/config.py rename to webex_skills/logging.py diff --git a/tests/skill/entities/.gitkeep b/webex_skills/models/__init__.py similarity index 100% rename from tests/skill/entities/.gitkeep rename to webex_skills/models/__init__.py diff --git a/webex_skills/models/http.py b/webex_skills/models/http.py new file mode 100644 index 0000000..f92f525 --- /dev/null +++ b/webex_skills/models/http.py @@ -0,0 +1,23 @@ +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel, constr + +from .mindmeld import DialogueState, Params + + +class InvokePayload(BaseModel): + signature: str + message: str + + +class SkillInvokeRequest(DialogueState): + challenge: constr(min_length=64, max_length=64) # type: ignore + + +# TODO: This should probably be like the InvokeRequest and just add a challenge to an existing type +class SkillInvokeResponse(BaseModel): + challenge: str + directives: List[Dict[Any, Any]] + frame: Optional[Dict[Any, Any]] = [] + params: Optional[Params] = {} + history: Optional[List[Dict[Any, Any]]] = [] diff --git a/webex_skills/models/mindmeld.py b/webex_skills/models/mindmeld.py new file mode 100644 index 0000000..d3c4b6d --- /dev/null +++ b/webex_skills/models/mindmeld.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from typing import Any, Dict, ForwardRef, List, Optional + +from pydantic import BaseModel, constr + +# Mindmeld's Request object is essentially just a ProcessedQuery object + the dialogue manager stuff + + +class Params(BaseModel): + target_dialogue_state: Optional[str] + time_zone: str + timestamp: int + # We enforce length via min/max length in addition to the regex so we get a more + # useful error message if the length is wrong. + language: constr(min_length=2, max_length=2, regex="^[a-zA-Z]{2}$") # type: ignore + locale: Optional[constr(regex="^[a-z]{2}([-_][A-Z]{2})?$")] # type: ignore + dynamic_resource: Optional[Dict[Any, Any]] = {} + allowed_intents: Optional[List[str]] = [] + + +class ProcessedQuery(BaseModel): + # This is a subset of the fields on ProcessedQuery. We will only ever receive a single transcript + # So we don't need to include any of the nbest_* fields or the confidence + text: str + domain: Optional[str] + intent: Optional[str] + # TODO: Add proper typing for entities (dict with keys for entity types and values) + entities: Optional[List[Dict[str, Any]]] = [] + + +_DialogueState = ForwardRef('DialogueState') + + +class DialogueState(BaseModel): + text: Optional[str] + context: Dict[Any, Any] + params: Params + frame: Dict[Any, Any] + history: Optional[List[_DialogueState]] = [] + # TODO: Unsure if I should put this directly on the State object or if our method should just be required + # to return a state and a list of directives + directives: Optional[List[Dict[Any, Any]]] = [] + + def update_history(self, old_state: DialogueState): + old_state_dict = DialogueState(**old_state.dict(exclude={'history'})) + self.history.append(old_state_dict) + + +DialogueState.update_forward_refs() diff --git a/webex_skills/settings.py b/webex_skills/settings.py new file mode 100644 index 0000000..339c7e0 --- /dev/null +++ b/webex_skills/settings.py @@ -0,0 +1,17 @@ +from pathlib import Path +from typing import Optional + +from pydantic import BaseSettings + + +class SkillSettings(BaseSettings): + skill_name: Optional[str] + private_key_path: Path = 'id_rsa.pem' + secret: Optional[str] + use_encryption: bool = True + log_level: str = 'INFO' + app_dir: Optional[str] = None + + class Config: + env_prefix = 'SKILLS_' + env_file = '.env' diff --git a/webex_skills/static/default_app.py b/webex_skills/static/default_app.py new file mode 100644 index 0000000..37e3a5e --- /dev/null +++ b/webex_skills/static/default_app.py @@ -0,0 +1,19 @@ +from webex_skills.api import SimpleAPI +from webex_skills.dialogue import responses +from webex_skills.models.mindmeld import DialogueState + +api = SimpleAPI() + + +@api.handle(default=True) +async def greet(current_state: DialogueState) -> DialogueState: + text = 'Hello I am a super simple skill' + new_state = current_state.copy() + + new_state.directives = [ + responses.Reply(text), + responses.Speak(text), + responses.Sleep(10), + ] + + return new_state diff --git a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/domains/greeting/exit/train.txt b/webex_skills/static/default_domains/greeting/exit/train.txt similarity index 100% rename from webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/domains/greeting/exit/train.txt rename to webex_skills/static/default_domains/greeting/exit/train.txt diff --git a/webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/domains/greeting/greet/train.txt b/webex_skills/static/default_domains/greeting/greet/train.txt similarity index 100% rename from webex_assistant_sdk/templates/mindmeld_template/{{cookiecutter.skill_name}}/{{cookiecutter.skill_name}}/domains/greeting/greet/train.txt rename to webex_skills/static/default_domains/greeting/greet/train.txt diff --git a/webex_skills/static/env.tmpl b/webex_skills/static/env.tmpl new file mode 100644 index 0000000..089ba04 --- /dev/null +++ b/webex_skills/static/env.tmpl @@ -0,0 +1,5 @@ +SKILLS_SKILL_NAME={skill_name} +SKILLS_SECRET={skill_secret} +SKILLS_USE_ENCRYPTION=1 +SKILLS_APP_DIR={app_dir} +SKILLS_PRIVATE_KEY_PATH={private_key_path} \ No newline at end of file diff --git a/webex_skills/static/mm_app.py b/webex_skills/static/mm_app.py new file mode 100644 index 0000000..2a5ad0e --- /dev/null +++ b/webex_skills/static/mm_app.py @@ -0,0 +1,19 @@ +from webex_skills.api import MindmeldAPI +from webex_skills.dialogue import responses +from webex_skills.models.mindmeld import DialogueState + +api = MindmeldAPI() + + +@api.handle(intent='greet') +async def greet(current_state: DialogueState) -> DialogueState: + text = 'Hello I am a super simple skill using NLP' + new_state = current_state.copy() + + new_state.directives = [ + responses.Reply(text), + responses.Speak(text), + responses.Sleep(10), + ] + + return new_state diff --git a/get_started_documentation/echo-skill/pyproject.toml b/webex_skills/static/pyproject.toml.tmpl similarity index 52% rename from get_started_documentation/echo-skill/pyproject.toml rename to webex_skills/static/pyproject.toml.tmpl index 58094f3..f94109b 100644 --- a/get_started_documentation/echo-skill/pyproject.toml +++ b/webex_skills/static/pyproject.toml.tmpl @@ -1,15 +1,16 @@ [tool.poetry] -name = "echo-skill" +name = "{skill_name}" version = "0.1.0" description = "" -authors = ["Juan Rodriguez "] +authors = [] [tool.poetry.dependencies] -python = "^3.7" -aiohttp = "^3.7.4" +python = "^3.8" +webex-skills = "^1.0.0" [tool.poetry.dev-dependencies] +pytest = "^5.2" [build-system] requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/webex_skills/supress_warnings.py b/webex_skills/supress_warnings.py new file mode 100644 index 0000000..d506383 --- /dev/null +++ b/webex_skills/supress_warnings.py @@ -0,0 +1,17 @@ +import warnings + + +class suppress_warnings: + def __init__(self): + self._showwarning = None + + def showwarning(self, *args, **kwargs): + pass + + def __enter__(self): + self._showwarning = warnings.showwarning + warnings.showwarning = self.showwarning + + def __exit__(self, exc_type, exc_val, exc_tb): + warnings.showwarning = self._showwarning + return diff --git a/webex_skills/types.py b/webex_skills/types.py new file mode 100644 index 0000000..894f9c0 --- /dev/null +++ b/webex_skills/types.py @@ -0,0 +1,13 @@ +from typing import Awaitable, Callable, Dict, Union + +from .dialogue.rules import MMDialogueStateRule, SimpleDialogueStateRule +from .models.mindmeld import DialogueState, ProcessedQuery + +DialogueQuery = Union[ProcessedQuery, str] +SimpleHandler = Callable[[DialogueState], Awaitable[DialogueState]] +QueryHandler = Callable[[DialogueState, DialogueQuery], Awaitable[DialogueState]] + +DialogueHandler = Union[SimpleHandler, QueryHandler] +DialogueRule = Union[SimpleDialogueStateRule, MMDialogueStateRule] + +RuleMap = Dict[DialogueRule, DialogueHandler]