diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3d5f0a7..d802d6e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -15,17 +15,23 @@ orbs:
jobs:
build:
docker:
- - image: circleci/python:3.7
+ # - image: circleci/python:3.7
+ - image: continuumio/miniconda3
steps:
- checkout
- restore_cache:
keys:
# - deps-{{ checksum "poetry.lock" }}
- deps-clear
+ - run:
+ name: Install Poetry
+ command: |
+ pip install poetry
+ poetry config virtualenvs.create false
- run:
name: Install Dependencies
command: |
- poetry install
+ poetry install --no-interaction
- run:
name: Run flake8
command: |
@@ -42,8 +48,8 @@ jobs:
# key: deps-{{ checksum "poetry.lock" }}
key: deps-clear
paths:
- - /home/circleci/.cache/pypoetry/virtualenvs
- - /home/circleci/.cache/.tox
+ - /root/.cache/pypoetry/virtualenvs
+ - /root/.cache/.tox
- run:
name: Running tests
command: |
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..2e913f4
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,23 @@
+# Inspired from:
+# https://github.com/nornir-automation/nornir/blob/develop/Dockerfile
+# https://github.com/microsoft/vscode-dev-containers/tree/master/containers/python-3-miniconda
+FROM continuumio/miniconda3
+
+WORKDIR /gns3fy
+ENV PATH="/root/.poetry/bin:$PATH" \
+ PYTHONDONTWRITEBYTECODE=1 \
+ PYTHONUNBUFFERED=1 \
+ DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update \
+ && apt-get install -yq curl git openssh-client less iproute2 procps iproute2 lsb-release make \
+ && curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python \
+ && poetry config virtualenvs.create false \
+ && apt-get autoremove -y \
+ && apt-get clean -y \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY pyproject.toml .
+COPY poetry.lock .
+
+RUN poetry install --no-interaction
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..5558107
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,27 @@
+{
+ "name": "gns3fy - Miniconda",
+ "context": "..",
+ "dockerFile": "Dockerfile",
+ // Set *default* container specific settings.json values on container create.
+ "settings": {
+ "terminal.integrated.shell.linux": "/bin/bash",
+ "python.pythonPath": "/opt/conda/bin/python",
+ "python.linting.enabled": true,
+ "python.linting.pylintPath": "/opt/conda/bin/pylint",
+ "python.formatting.blackPath": "/opt/conda/bin/black",
+ "python.formatting.provider": "black",
+ "python.linting.pylintEnabled": false,
+ "python.linting.flake8Path": "/opt/conda/bin/flake8",
+ "python.linting.flake8Enabled": true,
+ },
+ // Add the IDs of extensions you want installed when the container is created.
+ "extensions": [
+ "ms-python.python"
+ ]
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "make docker-settings",
+ // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
+ // "remoteUser": "vscode"
+}
diff --git a/Makefile b/Makefile
index 70489af..235decb 100644
--- a/Makefile
+++ b/Makefile
@@ -19,3 +19,9 @@ build:
publish:
poetry publish
+
+docker-settings:
+ cp .vscode/docker-settings.json .vscode/settings.json
+
+local-settings:
+ cp .vscode/local-settings.json .vscode/settings.json
diff --git a/README.md b/README.md
index 2f8ee6e..ca5bf41 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+# gns3fy
+
[![Circle CI](https://circleci.com/gh/davidban77/gns3fy/tree/develop.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/davidban77/gns3fy/tree/develop)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
[![codecov](https://codecov.io/gh/davidban77/gns3fy/branch/develop/graph/badge.svg)](https://codecov.io/gh/davidban77/gns3fy)
@@ -6,8 +8,6 @@
[![pypi](https://img.shields.io/pypi/v/gns3fy.svg)](https://pypi.python.org/pypi/gns3fy)
[![versions](https://img.shields.io/pypi/pyversions/gns3fy.svg)](https://github.com/davidban77/gns3fy)
-
-# gns3fy
Python wrapper around [GNS3 Server API](http://api.gns3.net/en/2.2/index.html). Minimal GNS3 version is 2.2.
Its main objective is to interact with the GNS3 server in a programatic way, so it can be integrated with the likes of Ansible, docker and scripts. Ideal for network CI/CD pipeline tooling.
@@ -28,7 +28,7 @@ Here are some examples where gns3fy is used in a programmatic way:
## Install
-```
+```shell
pip install gns3fy
```
diff --git a/docs/content/about/changelog.md b/docs/content/about/changelog.md
index 01d17e4..8e7753b 100644
--- a/docs/content/about/changelog.md
+++ b/docs/content/about/changelog.md
@@ -1,54 +1,68 @@
-# Upgrading
+# Upgrading and releases history
-```
+```shell
pip install -U gns3fy
```
-# Releases
+## Releases
-## 0.6.0
+### 0.7.0
+
+**Enhancement:**
+
+- Ability to manipulate SVGs. Added new methods: `create_drawing`, `update_drawing`, `delete_drawing` and `get_drawing`. Ref #66
+- Added a `drawing_utils.py` module that have some helper functions to generate SVGs in the Project: `generate_rectangle_svg`, `generate_line_svg` and `generate_ellipse_svg`. Also `parsed_x` and `parsed_y` that helps positions the Nodes and drawings in the canvas.
+- Python 3.8 support. Ref #68
+- Added `upload_compute_image` to the `Gns3Connector` object. Thanks @skeiffer for the contribution. Ref #62
+
+**Fix:**
+
+- Fixes issue closing Project. Ref #71
+- Update poetry and fix CI. Ref #64
+
+### 0.6.0
**Enhancement:**
- Added `drawings` attribute. Used to gather information from `Drawing` endpoint, and for that there is also the `get_drawings` method.
-- Added `arrange_nodes_circular` method, which as the name indicates it will arrange the nodes configured on a project in a circular fashion.
+- Added `arrange_nodes_circular` method, which as the name indicates it will arrange the nodes configured on a project in a circular fashion. Thanks @Krlosromero for the contribution.
-## 0.5.2
+### 0.5.2
**Enhancement:**
- Added `restore_snapshot` to the available snapshot methods of a project
-## 0.5.1
+### 0.5.1
**Fix:**
- Argument specification for project snapshot methods
- Cred argument for user-based authentication with a GNS3 server
-## 0.5.0
+### 0.5.0
**New features:**
- Extended templates functionality with `create_template`, `update_template` and `delete_template`. Which can be used for migrating templates between GNS3 servers
- Added compute endpoint get method from the REST API. [Compute endpoint](http://api.gns3.net/en/2.2/api/v2/controller/compute.html)
- - `get_computes`: Retrieves attributes and characteristics of the GNS3 server compute that will run the emulations.
- - `get_compute_images`: Lists images configured for a specific emulator on a compute.
- - `get_compute_ports`: Lists configured and used console ports and UDP ports on a compute.
+ - `get_computes`: Retrieves attributes and characteristics of the GNS3 server compute that will run the emulations.
+ - `get_compute_images`: Lists images configured for a specific emulator on a compute.
+ - `get_compute_ports`: Lists configured and used console ports and UDP ports on a compute.
- Added projects snapshots attribute and methods. [Snapshots endpoint](http://api.gns3.net/en/2.2/api/v2/controller/snapshot.html)
- - `snapshots`: Attribute that stores snapshots names, IDs and created times of a project. Updated by the `get_snapshots` method.
- - `get_snapshot`: Retrieves an specific snapshot information.
- - `create_snapshot` and `delete_snapshot`: Creates/Delete an specific snapshot
+ - `snapshots`: Attribute that stores snapshots names, IDs and created times of a project. Updated by the `get_snapshots` method.
+ - `get_snapshot`: Retrieves an specific snapshot information.
+ - `create_snapshot` and `delete_snapshot`: Creates/Delete an specific snapshot
-## 0.4.1
+### 0.4.1
**Fix:**
- Dependency of python to start at version 3.6
-## 0.4.0
+### 0.4.0
**New features:**
@@ -58,16 +72,16 @@ pip install -U gns3fy
- A "template not found" message, when creating a `Node` specifiying a missing/wrong template name.
-## 0.3.0
+### 0.3.0
**Enhancement:**
-- `tox` for pipeline testing. https://github.com/davidban77/gns3fy/pull/15
-- `projects_summary` and `templates_summary` methods for `Gns3Connector`. https://github.com/davidban77/gns3fy/pull/17
-- Improved `nodes_inventory` method. https://github.com/davidban77/gns3fy/pull/23
-- Refactor of `Node` creation, basically changed the API endpoint from Node to Template. https://github.com/davidban77/gns3fy/pull/27
+- `tox` for pipeline testing. [PR-15](https://github.com/davidban77/gns3fy/pull/15)
+- `projects_summary` and `templates_summary` methods for `Gns3Connector`. [PR-17](https://github.com/davidban77/gns3fy/pull/17)
+- Improved `nodes_inventory` method. [PR-23](https://github.com/davidban77/gns3fy/pull/23)
+- Refactor of `Node` creation, basically changed the API endpoint from Node to Template. [PR-27](https://github.com/davidban77/gns3fy/pull/27)
-## 0.2.0
+### 0.2.0
**New features:**
@@ -77,15 +91,16 @@ pip install -U gns3fy
- Created the `docs`
- Improved the tests and coverage
- Added CircleCI with the following checks:
- - flake8
- - black formatting
- - pytest
+ - flake8
+ - black formatting
+ - pytest
-## 0.1.1
+### 0.1.1
**Enhancement:**
+
- Adding `Gns3Connector` method `get_version`
-## 0.1.0
+### 0.1.0
- Initial Push
diff --git a/gns3fy/drawing_utils.py b/gns3fy/drawing_utils.py
new file mode 100644
index 0000000..4b39606
--- /dev/null
+++ b/gns3fy/drawing_utils.py
@@ -0,0 +1,61 @@
+"""
+Functions used as helpers for drawing objects in a GNS3 Project.
+"""
+
+
+def generate_rectangle_svg(
+ height: int = 100,
+ width: int = 200,
+ fill: str = "#ffffff",
+ fill_opacity: float = 1.0,
+ stroke: str = "#000000",
+ stroke_width: int = 2,
+) -> str:
+ return (
+ f''
+ )
+
+
+def generate_ellipse_svg(
+ height: float = 200.0,
+ width: float = 200.0,
+ cx: int = 100,
+ cy: int = 100,
+ fill: str = "#ffffff",
+ fill_opacity: float = 1.0,
+ rx: int = 100,
+ ry: int = 100,
+ stroke: str = "#000000",
+ stroke_width: int = 2,
+) -> str:
+ return (
+ f''
+ )
+
+
+def generate_line_svg(
+ height: int = 0,
+ width: int = 200,
+ x1: int = 0,
+ x2: int = 200,
+ y1: int = 0,
+ y2: int = 0,
+ stroke: str = "#000000",
+ stroke_width: int = 2,
+) -> str:
+ return (
+ f''
+ )
+
+
+def parsed_x(x: int, obj_width: int = 100) -> int:
+ return x * obj_width
+
+
+def parsed_y(y: int, obj_height: int = 100) -> int:
+ return (y * obj_height) * -1
diff --git a/gns3fy/gns3fy.py b/gns3fy/gns3fy.py
index b82361e..5ecb4c8 100644
--- a/gns3fy/gns3fy.py
+++ b/gns3fy/gns3fy.py
@@ -1,5 +1,7 @@
+import os
import time
import requests
+from functools import wraps
from urllib.parse import urlparse
from requests import HTTPError
from dataclasses import field
@@ -442,6 +444,23 @@ def get_compute_images(self, emulator, compute_id="local"):
_url = f"{self.base_url}/computes/{compute_id}/{emulator}/images"
return self.http_call("get", _url).json()
+ def upload_compute_image(self, emulator, file_path, compute_id="local"):
+ """
+ uploads an image for use by a compute.
+
+ **Required Attributes:**
+
+ - `emulator`: the likes of 'qemu', 'iou', 'docker' ...
+ - `file_path`: path of file to be uploaded
+ - `compute_id` By default is 'local'
+ """
+ if not os.path.exists(file_path):
+ raise FileNotFoundError(f"Could not find file: {file_path}")
+
+ _filename = os.path.basename(file_path)
+ _url = f"{self.base_url}/computes/{compute_id}/{emulator}/images/{_filename}"
+ self.http_call("post", _url, data=open(file_path, "rb"))
+
def get_compute_ports(self, compute_id="local"):
"""
Returns ports used and configured by a compute.
@@ -458,6 +477,45 @@ def get_compute_ports(self, compute_id="local"):
return self.http_call("get", _url).json()
+def verify_connector_and_id(f):
+ """
+ Main checker for connector object and respective object's ID for their retrieval
+ or actions methods.
+ """
+
+ @wraps(f)
+ def wrapper(self, *args, **kwargs):
+ if not self.connector:
+ raise ValueError("Gns3Connector not assigned under 'connector'")
+ if not self.project_id:
+ raise ValueError("Need to submit project_id")
+ # Checks for Node
+ if self.__class__.__name__ == "Node":
+ if not self.node_id:
+ if not self.name:
+ raise ValueError("Need to either submit node_id or name")
+
+ # Try to retrieve the node_id
+ _url = f"{self.connector.base_url}/projects/{self.project_id}/nodes"
+ _response = self.connector.http_call("get", _url)
+
+ extracted = [
+ node for node in _response.json() if node["name"] == self.name
+ ]
+ if len(extracted) > 1:
+ raise ValueError(
+ "Multiple nodes found with same name. Need to submit node_id"
+ )
+ self.node_id = extracted[0]["node_id"]
+ # Checks for Link
+ if self.__class__.__name__ == "Link":
+ if not self.link_id:
+ raise ValueError("Need to submit link_id")
+ return f(self, *args, **kwargs)
+
+ return wrapper
+
+
@dataclass(config=Config)
class Link:
"""
@@ -519,14 +577,7 @@ def _update(self, data_dict):
if k in self.__dict__.keys():
self.__setattr__(k, v)
- def _verify_before_action(self):
- if not self.connector:
- raise ValueError("Gns3Connector not assigned under 'connector'")
- if not self.project_id:
- raise ValueError("Need to submit project_id")
- if not self.link_id:
- raise ValueError("Need to submit link_id")
-
+ @verify_connector_and_id
def get(self):
"""
Retrieves the information from the link endpoint.
@@ -537,8 +588,6 @@ def get(self):
- `connector`
- `link_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/links/{self.link_id}"
)
@@ -547,6 +596,7 @@ def get(self):
# Update object
self._update(_response.json())
+ @verify_connector_and_id
def delete(self):
"""
Deletes a link endpoint from the project. It sets to `None` the attributes
@@ -558,8 +608,6 @@ def delete(self):
- `connector`
- `link_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/links/{self.link_id}"
)
@@ -709,26 +757,7 @@ def _update(self, data_dict):
if k in self.__dict__.keys():
self.__setattr__(k, v)
- def _verify_before_action(self):
- if not self.connector:
- raise ValueError("Gns3Connector not assigned under 'connector'")
- if not self.project_id:
- raise ValueError("Need to submit project_id")
- if not self.node_id:
- if not self.name:
- raise ValueError("Need to either submit node_id or name")
-
- # Try to retrieve the node_id
- _url = f"{self.connector.base_url}/projects/{self.project_id}/nodes"
- _response = self.connector.http_call("get", _url)
-
- extracted = [node for node in _response.json() if node["name"] == self.name]
- if len(extracted) > 1:
- raise ValueError(
- "Multiple nodes found with same name. Need to submit node_id"
- )
- self.node_id = extracted[0]["node_id"]
-
+ @verify_connector_and_id
def get(self, get_links=True):
"""
Retrieves the node information. When `get_links` is `True` it also retrieves the
@@ -740,8 +769,6 @@ def get(self, get_links=True):
- `connector`
- `node_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes/{self.node_id}"
)
@@ -753,6 +780,7 @@ def get(self, get_links=True):
if get_links:
self.get_links()
+ @verify_connector_and_id
def get_links(self):
"""
Retrieves the links of the respective node. They will be saved at the `links`
@@ -764,8 +792,6 @@ def get_links(self):
- `connector`
- `node_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes"
f"/{self.node_id}/links"
@@ -778,6 +804,7 @@ def get_links(self):
for _link in _response.json():
self.links.append(Link(connector=self.connector, **_link))
+ @verify_connector_and_id
def start(self):
"""
Starts the node.
@@ -788,8 +815,6 @@ def start(self):
- `connector`
- `node_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes"
f"/{self.node_id}/start"
@@ -802,6 +827,7 @@ def start(self):
else:
self.get()
+ @verify_connector_and_id
def stop(self):
"""
Stops the node.
@@ -812,8 +838,6 @@ def stop(self):
- `connector`
- `node_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes"
f"/{self.node_id}/stop"
@@ -826,6 +850,7 @@ def stop(self):
else:
self.get()
+ @verify_connector_and_id
def reload(self):
"""
Reloads the node.
@@ -836,8 +861,6 @@ def reload(self):
- `connector`
- `node_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes"
f"/{self.node_id}/reload"
@@ -850,6 +873,7 @@ def reload(self):
else:
self.get()
+ @verify_connector_and_id
def suspend(self):
"""
Suspends the node.
@@ -860,8 +884,6 @@ def suspend(self):
- `connector`
- `node_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes"
f"/{self.node_id}/suspend"
@@ -874,6 +896,7 @@ def suspend(self):
else:
self.get()
+ @verify_connector_and_id
def update(self, **kwargs):
"""
Updates the node instance by passing the keyword arguments of the attributes
@@ -892,8 +915,6 @@ def update(self, **kwargs):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes/{self.node_id}"
)
@@ -965,6 +986,7 @@ def create(self):
# Update the node attributes based on cached data
self.update(**cached_data)
+ @verify_connector_and_id
def delete(self):
"""
Deletes the node from the project. It sets to `None` the attributes `node_id`
@@ -976,8 +998,6 @@ def delete(self):
- `connector`
- `node_id`
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes/{self.node_id}"
)
@@ -988,6 +1008,7 @@ def delete(self):
self.node_id = None
self.name = None
+ @verify_connector_and_id
def get_file(self, path):
"""
Retrieve a file in the node directory.
@@ -998,8 +1019,6 @@ def get_file(self, path):
- `connector`
- `path`: Node's relative path of the file
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes/{self.node_id}"
f"/files/{path}"
@@ -1007,6 +1026,7 @@ def get_file(self, path):
return self.connector.http_call("get", _url).text
+ @verify_connector_and_id
def write_file(self, path, data):
"""
Places a file content on a specified node file path. Used mainly for docker
@@ -1030,8 +1050,6 @@ def write_file(self, path, data):
- `path`: Node's relative path of the file
- `data`: Data to be included in the file
"""
- self._verify_before_action()
-
_url = (
f"{self.connector.base_url}/projects/{self.project_id}/nodes/{self.node_id}"
f"/files/{path}"
@@ -1125,12 +1143,6 @@ def _update(self, data_dict):
if k in self.__dict__.keys():
self.__setattr__(k, v)
- def _verify_before_action(self):
- if not self.connector:
- raise ValueError("Gns3Connector not assigned under 'connector'")
- if not self.project_id:
- raise ValueError("Need to submit project_id")
-
def get(self, get_links=True, get_nodes=True, get_stats=True):
"""
Retrieves the projects information.
@@ -1139,6 +1151,10 @@ def get(self, get_links=True, get_nodes=True, get_stats=True):
- `get_nodes`: When true it also queries for the nodes inside the project
- `get_stats`: When true it also queries for the stats inside the project
+ It `get_stats` is set to `True`, it also verifies if snapshots and drawings are
+ inside the project and stores them in their respective attributes
+ (`snapshots` and `drawings`)
+
**Required Attributes:**
- `connector`
@@ -1171,6 +1187,8 @@ def get(self, get_links=True, get_nodes=True, get_stats=True):
self.get_stats()
if self.stats.get("snapshots", 0) > 0:
self.get_snapshots()
+ if self.stats.get("drawings", 0) > 0:
+ self.get_drawings()
if get_nodes:
self.get_nodes()
if get_links:
@@ -1204,6 +1222,7 @@ def create(self):
# Now update it
self._update(_response.json())
+ @verify_connector_and_id
def update(self, **kwargs):
"""
Updates the project instance by passing the keyword arguments of the attributes
@@ -1222,8 +1241,6 @@ def update(self, **kwargs):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}"
# TODO: Verify that the passed kwargs are supported ones
@@ -1232,6 +1249,7 @@ def update(self, **kwargs):
# Update object
self._update(_response.json())
+ @verify_connector_and_id
def delete(self):
"""
Deletes the project from the server. It sets to `None` the attributes
@@ -1242,8 +1260,6 @@ def delete(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}"
self.connector.http_call("delete", _url)
@@ -1251,6 +1267,7 @@ def delete(self):
self.project_id = None
self.name = None
+ @verify_connector_and_id
def close(self):
"""
Closes the project on the server.
@@ -1260,15 +1277,15 @@ def close(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/close"
_response = self.connector.http_call("post", _url)
# Update object
- self._update(_response.json())
+ if _response.status_code == 204:
+ self.status = "closed"
+ @verify_connector_and_id
def open(self):
"""
Opens the project on the server.
@@ -1278,8 +1295,6 @@ def open(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/open"
_response = self.connector.http_call("post", _url)
@@ -1287,6 +1302,7 @@ def open(self):
# Update object
self._update(_response.json())
+ @verify_connector_and_id
def get_stats(self):
"""
Retrieve the stats of the project.
@@ -1296,8 +1312,6 @@ def get_stats(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/stats"
_response = self.connector.http_call("get", _url)
@@ -1305,6 +1319,7 @@ def get_stats(self):
# Update object
self.stats = _response.json()
+ @verify_connector_and_id
def get_file(self, path):
"""
Retrieve a file in the project directory. Beware you have warranty to be able to
@@ -1316,12 +1331,11 @@ def get_file(self, path):
- `connector`
- `path`: Project's relative path of the file
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/files/{path}"
return self.connector.http_call("get", _url).text
+ @verify_connector_and_id
def write_file(self, path, data):
"""
Places a file content on a specified project file path. Beware you have warranty
@@ -1344,12 +1358,11 @@ def write_file(self, path, data):
- `path`: Project's relative path of the file
- `data`: Data to be included in the file
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/files/{path}"
self.connector.http_call("post", _url, data=data)
+ @verify_connector_and_id
def get_nodes(self):
"""
Retrieve the nodes of the project.
@@ -1359,8 +1372,6 @@ def get_nodes(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/nodes"
_response = self.connector.http_call("get", _url)
@@ -1373,6 +1384,7 @@ def get_nodes(self):
_n.project_id = self.project_id
self.nodes.append(_n)
+ @verify_connector_and_id
def get_links(self):
"""
Retrieve the links of the project.
@@ -1382,8 +1394,6 @@ def get_links(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/links"
_response = self.connector.http_call("get", _url)
@@ -1396,6 +1406,7 @@ def get_links(self):
_l.project_id = self.project_id
self.links.append(_l)
+ @verify_connector_and_id
def start_nodes(self, poll_wait_time=5):
"""
Starts all the nodes inside the project.
@@ -1408,8 +1419,6 @@ def start_nodes(self, poll_wait_time=5):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/nodes/start"
self.connector.http_call("post", _url)
@@ -1418,6 +1427,7 @@ def start_nodes(self, poll_wait_time=5):
time.sleep(poll_wait_time)
self.get_nodes()
+ @verify_connector_and_id
def stop_nodes(self, poll_wait_time=5):
"""
Stops all the nodes inside the project.
@@ -1430,8 +1440,6 @@ def stop_nodes(self, poll_wait_time=5):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/nodes/stop"
self.connector.http_call("post", _url)
@@ -1440,6 +1448,7 @@ def stop_nodes(self, poll_wait_time=5):
time.sleep(poll_wait_time)
self.get_nodes()
+ @verify_connector_and_id
def reload_nodes(self, poll_wait_time=5):
"""
Reloads all the nodes inside the project.
@@ -1452,8 +1461,6 @@ def reload_nodes(self, poll_wait_time=5):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/nodes/reload"
self.connector.http_call("post", _url)
@@ -1462,6 +1469,7 @@ def reload_nodes(self, poll_wait_time=5):
time.sleep(poll_wait_time)
self.get_nodes()
+ @verify_connector_and_id
def suspend_nodes(self, poll_wait_time=5):
"""
Suspends all the nodes inside the project.
@@ -1474,8 +1482,6 @@ def suspend_nodes(self, poll_wait_time=5):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/nodes/suspend"
self.connector.http_call("post", _url)
@@ -1765,6 +1771,7 @@ def create_link(self, node_a, port_a, node_b, port_b):
self.links.append(_link)
print(f"Created Link-ID: {_link.link_id} -- Type: {_link.link_type}")
+ @verify_connector_and_id
def get_snapshots(self):
"""
Retrieves list of snapshots of the project
@@ -1774,8 +1781,6 @@ def get_snapshots(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/snapshots"
response = self.connector.http_call("get", _url)
@@ -1810,6 +1815,7 @@ def get_snapshot(self, name=None, snapshot_id=None):
else:
raise ValueError("name or snapshot_id must be provided")
+ @verify_connector_and_id
def create_snapshot(self, name):
"""
Creates a snapshot of the project
@@ -1823,8 +1829,6 @@ def create_snapshot(self, name):
- `name`
"""
- self._verify_before_action()
-
self.get_snapshots()
_snapshot = self.get_snapshot(name=name)
@@ -1840,6 +1844,7 @@ def create_snapshot(self, name):
self.snapshots.append(_snapshot)
print(f"Created snapshot: {_snapshot['name']}")
+ @verify_connector_and_id
def delete_snapshot(self, name=None, snapshot_id=None):
"""
Deletes a snapshot of the project
@@ -1853,8 +1858,6 @@ def delete_snapshot(self, name=None, snapshot_id=None):
- `name` or `snapshot_id`
"""
- self._verify_before_action()
-
self.get_snapshots()
_snapshot = self.get_snapshot(name=name, snapshot_id=snapshot_id)
@@ -1870,6 +1873,7 @@ def delete_snapshot(self, name=None, snapshot_id=None):
self.get_snapshots()
+ @verify_connector_and_id
def restore_snapshot(self, name=None, snapshot_id=None):
"""
Restore a snapshot from disk
@@ -1883,8 +1887,6 @@ def restore_snapshot(self, name=None, snapshot_id=None):
- `name` or `snapshot_id`
"""
- self._verify_before_action()
-
self.get_snapshots()
_snapshot = self.get_snapshot(name=name, snapshot_id=snapshot_id)
@@ -1929,6 +1931,31 @@ def arrange_nodes_circular(self, radius=120):
_y = int(radius * (-cos(_angle * index)))
n.update(x=_x, y=_y)
+ def get_drawing(self, drawing_id=None):
+ """
+ Returns the drawing by searching for the `svg` or the `drawing_id`.
+
+ **Required Attributes:**
+
+ - `project_id`
+ - `connector`
+
+ **Required keyword arguments:**
+ - `svg` or `drawing_id`
+ """
+ if not self.drawings:
+ self.get_drawings()
+
+ try:
+ return next(
+ _drawing
+ for _drawing in self.drawings
+ if _drawing["drawing_id"] == drawing_id
+ )
+ except StopIteration:
+ return None
+
+ @verify_connector_and_id
def get_drawings(self):
"""
Retrieves list of drawings of the project
@@ -1938,9 +1965,109 @@ def get_drawings(self):
- `project_id`
- `connector`
"""
- self._verify_before_action()
-
_url = f"{self.connector.base_url}/projects/{self.project_id}/drawings"
_response = self.connector.http_call("get", _url)
self.drawings = _response.json()
+
+ @verify_connector_and_id
+ def create_drawing(self, svg, locked=False, x=10, y=10, z=1):
+ """
+ Creates a drawing on the project
+
+ **Required Project instance attributes:**
+
+ - `project_id`
+ - `connector`
+ """
+ _url = f"{self.connector.base_url}/projects/{self.project_id}/drawings"
+
+ response = self.connector.http_call(
+ "post", _url, json_data=dict(svg=svg, locked=locked, x=x, y=y, z=z)
+ )
+
+ _drawing = response.json()
+
+ self.drawings.append(_drawing)
+ print(f"Created drawing: {_drawing['drawing_id']}")
+
+ @verify_connector_and_id
+ def update_drawing(self, drawing_id, svg=None, locked=None, x=None, y=None, z=None):
+ """
+ Updates a drawing on the project
+
+ **Required Project instance attributes:**
+
+ - `project_id`
+ - `connector`
+ """
+ _url = (
+ f"{self.connector.base_url}/projects/{self.project_id}/drawings/"
+ f"{drawing_id}"
+ )
+
+ if svg is None:
+ svg = [
+ draw["svg"]
+ for draw in self.drawings
+ if draw["drawing_id"] == drawing_id
+ ][0]
+
+ if locked is None:
+ locked = [
+ draw["locked"]
+ for draw in self.drawings
+ if draw["drawing_id"] == drawing_id
+ ][0]
+
+ if x is None:
+ x = [
+ draw["x"] for draw in self.drawings if draw["drawing_id"] == drawing_id
+ ][0]
+
+ if y is None:
+ y = [
+ draw["y"] for draw in self.drawings if draw["drawing_id"] == drawing_id
+ ][0]
+
+ if z is None:
+ z = [
+ draw["z"] for draw in self.drawings if draw["drawing_id"] == drawing_id
+ ][0]
+
+ response = self.connector.http_call(
+ "put", _url, json_data=dict(svg=svg, locked=locked, x=x, y=y, z=z)
+ )
+
+ self.get_drawings()
+
+ return response.json()
+
+ @verify_connector_and_id
+ def delete_drawing(self, drawing_id=None):
+ """
+ Deletes a drawing of the project
+
+ **Required Project instance attributes:**
+
+ - `project_id`
+ - `connector`
+
+ **Required keyword aguments:**
+
+ - `drawing_id`
+ """
+ self.get_drawings()
+
+ _drawing = self.get_drawing(drawing_id=drawing_id)
+ if not _drawing:
+ raise ValueError("drawing not found")
+
+ _url = (
+ f"{self.connector.base_url}/projects/{self.project_id}/drawings/"
+ f"{_drawing['drawing_id']}"
+ )
+
+ self.connector.http_call("delete", _url)
+
+ self.get_drawings()
diff --git a/poetry.lock b/poetry.lock
index aa3bcd0..6ba269f 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -18,6 +18,7 @@ version = "0.1.0"
[[package]]
category = "dev"
description = "Atomic file writes."
+marker = "sys_platform == \"win32\""
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@@ -31,6 +32,12 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.3.0"
+[package.extras]
+azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
+dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
+docs = ["sphinx", "zope.interface"]
+tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+
[[package]]
category = "dev"
description = "Specifications for callback functions passed in to an API"
@@ -56,13 +63,16 @@ regex = "*"
toml = ">=0.9.4"
typed-ast = ">=1.4.0"
+[package.extras]
+d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
+
[[package]]
category = "main"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
optional = false
python-versions = "*"
-version = "2019.9.11"
+version = "2019.11.28"
[[package]]
category = "main"
@@ -83,19 +93,22 @@ version = "7.0"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
-marker = "sys_platform == \"win32\""
+marker = "sys_platform == \"win32\" or platform_system == \"Windows\""
name = "colorama"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.4.1"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.4.3"
[[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
-version = "4.5.4"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "5.0.3"
+
+[package.extras]
+toml = ["toml"]
[[package]]
category = "main"
@@ -112,18 +125,15 @@ description = "Decorators for Humans"
name = "decorator"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.4.1"
+version = "4.4.2"
[[package]]
-category = "main"
-description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
-name = "deprecated"
+category = "dev"
+description = "Distribution utilities"
+name = "distlib"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.2.6"
-
-[package.dependencies]
-wrapt = ">=1.10,<2"
+python-versions = "*"
+version = "0.3.0"
[[package]]
category = "dev"
@@ -155,13 +165,21 @@ mccabe = ">=0.6.0,<0.7.0"
pycodestyle = ">=2.5.0,<2.6.0"
pyflakes = ">=2.1.0,<2.2.0"
+[[package]]
+category = "dev"
+description = "Clean single-source support for Python 3 and 2"
+name = "future"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "0.18.2"
+
[[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.8"
+version = "2.9"
[[package]]
category = "dev"
@@ -169,19 +187,44 @@ description = "Read metadata from Python packages"
marker = "python_version < \"3.8\""
name = "importlib-metadata"
optional = false
-python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3"
-version = "0.23"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+version = "1.5.0"
[package.dependencies]
zipp = ">=0.5"
+[package.extras]
+docs = ["sphinx", "rst.linker"]
+testing = ["packaging", "importlib-resources"]
+
+[[package]]
+category = "dev"
+description = "Read resources from Python packages"
+marker = "python_version < \"3.7\""
+name = "importlib-resources"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+version = "1.2.0"
+
+[package.dependencies]
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = "*"
+
+[package.dependencies.zipp]
+python = "<3.8"
+version = ">=0.4"
+
+[package.extras]
+docs = ["sphinx", "docutils (0.12)", "rst.linker"]
+
[[package]]
category = "dev"
description = "IPython: Productive Interactive Computing"
name = "ipython"
optional = false
-python-versions = ">=3.5"
-version = "7.9.0"
+python-versions = ">=3.6"
+version = "7.13.0"
[package.dependencies]
appnope = "*"
@@ -191,11 +234,22 @@ decorator = "*"
jedi = ">=0.10"
pexpect = "*"
pickleshare = "*"
-prompt-toolkit = ">=2.0.0,<2.1.0"
+prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
pygments = "*"
setuptools = ">=18.5"
traitlets = ">=4.2"
+[package.extras]
+all = ["numpy (>=1.14)", "testpath", "notebook", "nose (>=0.10.1)", "nbconvert", "requests", "ipywidgets", "qtconsole", "ipyparallel", "Sphinx (>=1.3)", "pygments", "nbformat", "ipykernel"]
+doc = ["Sphinx (>=1.3)"]
+kernel = ["ipykernel"]
+nbconvert = ["nbconvert"]
+nbformat = ["nbformat"]
+notebook = ["notebook", "ipywidgets"]
+parallel = ["ipyparallel"]
+qtconsole = ["qtconsole"]
+test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"]
+
[[package]]
category = "dev"
description = "Vestigial utilities from IPython"
@@ -210,22 +264,29 @@ description = "An autocompletion tool for Python that can be used for text edito
name = "jedi"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.15.1"
+version = "0.16.0"
[package.dependencies]
-parso = ">=0.5.0"
+parso = ">=0.5.2"
+
+[package.extras]
+qa = ["flake8 (3.7.9)"]
+testing = ["colorama (0.4.1)", "docopt", "pytest (>=3.9.0,<5.0.0)"]
[[package]]
category = "dev"
description = "A very fast and expressive template engine."
name = "jinja2"
optional = false
-python-versions = "*"
-version = "2.10.3"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.11.1"
[package.dependencies]
MarkupSafe = ">=0.23"
+[package.extras]
+i18n = ["Babel (>=0.8)"]
+
[[package]]
category = "dev"
description = "Python LiveReload is an awesome tool for web developers"
@@ -238,17 +299,39 @@ version = "2.6.1"
six = "*"
tornado = "*"
+[[package]]
+category = "dev"
+description = "A Python implementation of Lunr.js"
+name = "lunr"
+optional = false
+python-versions = "*"
+version = "0.5.6"
+
+[package.dependencies]
+future = ">=0.16.0"
+six = ">=1.11.0"
+
+[package.dependencies.nltk]
+optional = true
+version = ">=3.2.5"
+
+[package.extras]
+languages = ["nltk (>=3.2.5)"]
+
[[package]]
category = "dev"
description = "Python implementation of Markdown."
name = "markdown"
optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
-version = "3.1.1"
+python-versions = ">=3.5"
+version = "3.2.1"
[package.dependencies]
setuptools = ">=36"
+[package.extras]
+testing = ["coverage", "pyyaml"]
+
[[package]]
category = "dev"
description = "Safely add untrusted strings to HTML/XML markup."
@@ -270,17 +353,21 @@ category = "dev"
description = "Project documentation with Markdown."
name = "mkdocs"
optional = false
-python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.0.4"
+python-versions = ">=3.5"
+version = "1.1"
[package.dependencies]
-Jinja2 = ">=2.7.1"
-Markdown = ">=2.3.1"
+Jinja2 = ">=2.10.1"
+Markdown = ">=3.2.1"
PyYAML = ">=3.10"
click = ">=3.3"
livereload = ">=2.5.1"
tornado = ">=5.0"
+[package.dependencies.lunr]
+extras = ["languages"]
+version = "0.5.6"
+
[[package]]
category = "dev"
description = "A clean responsive theme for the MkDocs static documentation site generator"
@@ -294,21 +381,118 @@ category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
-python-versions = ">=3.4"
-version = "7.2.0"
+python-versions = ">=3.5"
+version = "8.2.0"
[[package]]
-category = "main"
-description = "Toolbox with useful Python classes and type magic."
-name = "nr.types"
+category = "dev"
+description = "Natural Language Toolkit"
+name = "nltk"
optional = false
python-versions = "*"
-version = "4.0.2"
+version = "3.4.5"
[package.dependencies]
-deprecated = "*"
six = "*"
-typing = "*"
+
+[package.extras]
+all = ["pyparsing", "scikit-learn", "python-crfsuite", "matplotlib", "scipy", "gensim", "requests", "twython", "numpy"]
+corenlp = ["requests"]
+machine_learning = ["gensim", "numpy", "python-crfsuite", "scikit-learn", "scipy"]
+plot = ["matplotlib"]
+tgrep = ["pyparsing"]
+twitter = ["twython"]
+
+[[package]]
+category = "dev"
+description = "Useful container datatypes for Python 2 and 3."
+name = "nr.collections"
+optional = false
+python-versions = "*"
+version = "0.0.1"
+
+[package.dependencies]
+"nr.metaclass" = ">=0.0.1,<0.1.0"
+six = ">=1.11.0,<2.0.0"
+
+[package.extras]
+test = ["nr.fs (>=1.5.0,<2.0.0)"]
+
+[[package]]
+category = "dev"
+description = "Bind structured data directly to typed objects."
+name = "nr.databind"
+optional = false
+python-versions = "*"
+version = "0.0.4"
+
+[package.dependencies]
+"nr.collections" = ">=0.0.1,<0.1.0"
+"nr.interface" = ">=0.0.1,<0.1.0"
+"nr.parsing.date" = ">=0.0.1,<0.1.0"
+"nr.pylang.utils" = ">=0.0.1,<0.1.0"
+"nr.stream" = ">=0.0.1,<0.1.0"
+
+[package.extras]
+test = ["pytest", "pyyaml"]
+
+[[package]]
+category = "dev"
+description = "Interface definitions for Python."
+name = "nr.interface"
+optional = false
+python-versions = "*"
+version = "0.0.1"
+
+[package.dependencies]
+"nr.collections" = ">=0.0.1,<0.1.0"
+"nr.metaclass" = ">=0.0.1,<0.1.0"
+"nr.pylang.utils" = ">=0.0.1,<0.1.0"
+six = ">=1.11.0,<2.0.0"
+
+[[package]]
+category = "dev"
+description = "Metaclass utilities."
+name = "nr.metaclass"
+optional = false
+python-versions = "*"
+version = "0.0.4"
+
+[[package]]
+category = "dev"
+description = "A simple and fast date parsing library. Uses dateutil for timezone offset support."
+name = "nr.parsing.date"
+optional = false
+python-versions = "*"
+version = "0.0.1"
+
+[package.dependencies]
+python-dateutil = ">=2.8.1,<3.0.0"
+
+[[package]]
+category = "dev"
+description = "Package description here."
+name = "nr.pylang.utils"
+optional = false
+python-versions = "*"
+version = "0.0.1"
+
+[package.dependencies]
+"nr.collections" = ">=0.0.1,<0.1.0"
+typing = ">=3.7.4.1,<4.0.0"
+
+[[package]]
+category = "dev"
+description = "Use iterators like Java streams."
+name = "nr.stream"
+optional = false
+python-versions = "*"
+version = "0.0.2"
+
+[package.dependencies]
+"nr.collections" = ">=0.0.1,<1.0.0"
+"nr.pylang.utils" = ">=0.0.1,<1.0.0"
+six = ">=1.11.0,<2.0.0"
[[package]]
category = "dev"
@@ -316,7 +500,7 @@ description = "Core utilities for Python packages"
name = "packaging"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "19.2"
+version = "20.1"
[package.dependencies]
pyparsing = ">=2.0.2"
@@ -327,16 +511,27 @@ category = "dev"
description = "A Python Parser"
name = "parso"
optional = false
-python-versions = "*"
-version = "0.5.1"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.6.2"
+
+[package.extras]
+testing = ["docopt", "pytest (>=3.0.7)"]
[[package]]
category = "dev"
description = "Utility library for gitignore style pattern matching of file paths."
name = "pathspec"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.6.0"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.7.0"
+
+[[package]]
+category = "dev"
+description = "File system general utilities"
+name = "pathtools"
+optional = false
+python-versions = "*"
+version = "0.1.2"
[[package]]
category = "dev"
@@ -345,7 +540,7 @@ marker = "sys_platform != \"win32\""
name = "pexpect"
optional = false
python-versions = "*"
-version = "4.7.0"
+version = "4.8.0"
[package.dependencies]
ptyprocess = ">=0.5"
@@ -364,23 +559,25 @@ description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.13.0"
+version = "0.13.1"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
+[package.extras]
+dev = ["pre-commit", "tox"]
+
[[package]]
category = "dev"
description = "Library for building powerful interactive command lines in Python"
name = "prompt-toolkit"
optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "2.0.10"
+python-versions = ">=3.6"
+version = "3.0.3"
[package.dependencies]
-six = ">=1.9.0"
wcwidth = "*"
[[package]]
@@ -398,7 +595,7 @@ description = "library with cross-python path, ini-parsing, io, code, log facili
name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.8.0"
+version = "1.8.1"
[[package]]
category = "dev"
@@ -414,15 +611,20 @@ description = "Data validation and settings management using python 3.6 type hin
name = "pydantic"
optional = false
python-versions = ">=3.6"
-version = "1.0"
+version = "1.4"
[package.dependencies]
[package.dependencies.dataclasses]
python = "<3.7"
version = ">=0.6"
+[package.extras]
+dotenv = ["python-dotenv (>=0.10.4)"]
+email = ["email-validator (>=1.0.3)"]
+typing_extensions = ["typing-extensions (>=3.7.2)"]
+
[[package]]
-category = "main"
+category = "dev"
description = "Create Python API documentation in Markdown format."
name = "pydoc-markdown"
optional = false
@@ -430,12 +632,15 @@ python-versions = "*"
version = "3.0.0"
[package.dependencies]
-"nr.types" = ">=4.0.0,<5.0.0"
-pyyaml = ">=3.12"
-six = ">=0.11.0"
+PyYAML = ">=5.3,<6.0.0"
+click = ">=7.0,<8.0.0"
+"nr.collections" = ">=0.0.1,<0.1.0"
+"nr.databind" = ">=0.0.4,<0.1.0"
+six = ">=1.11.0,<2.0.0"
+watchdog = ">=0.10.2,<1.0.0"
[package.source]
-reference = "2db72234c44d7666d0001d2c2aa7f906efcb572f"
+reference = "ccac72d01d16af2f3ba267250c5404e8a982d38b"
type = "git"
url = "https://github.com/NiklasRosenstein/pydoc-markdown.git"
[[package]]
@@ -452,7 +657,7 @@ description = "Pygments is a syntax highlighting package written in Python."
name = "pygments"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.4.2"
+version = "2.5.2"
[[package]]
category = "dev"
@@ -460,7 +665,7 @@ description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "2.4.2"
+version = "2.4.6"
[[package]]
category = "dev"
@@ -468,7 +673,7 @@ description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.5"
-version = "5.2.2"
+version = "5.3.5"
[package.dependencies]
atomicwrites = ">=1.0"
@@ -484,6 +689,10 @@ wcwidth = "*"
python = "<3.8"
version = ">=0.12"
+[package.extras]
+checkqa-mypy = ["mypy (v0.761)"]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
[[package]]
category = "dev"
description = "Pytest plugin for measuring coverage."
@@ -496,13 +705,27 @@ version = "2.8.1"
coverage = ">=4.4"
pytest = ">=3.6"
+[package.extras]
+testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"]
+
[[package]]
-category = "main"
+category = "dev"
+description = "Extensions to the standard Python datetime module"
+name = "python-dateutil"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+version = "2.8.1"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+category = "dev"
description = "YAML parser and emitter for Python"
name = "pyyaml"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "5.1.2"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "5.3"
[[package]]
category = "dev"
@@ -510,7 +733,7 @@ description = "Alternative regular expression module, to replace re."
name = "regex"
optional = false
python-versions = "*"
-version = "2019.11.1"
+version = "2020.2.20"
[[package]]
category = "main"
@@ -518,14 +741,18 @@ description = "Python HTTP for Humans."
name = "requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.22.0"
+version = "2.23.0"
[package.dependencies]
certifi = ">=2017.4.17"
-chardet = ">=3.0.2,<3.1.0"
-idna = ">=2.5,<2.9"
+chardet = ">=3.0.2,<4"
+idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
+[package.extras]
+security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+
[[package]]
category = "dev"
description = "Mock out responses from the requests package"
@@ -538,13 +765,17 @@ version = "1.7.0"
requests = ">=2.3,<3"
six = "*"
+[package.extras]
+fixture = ["fixtures"]
+test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"]
+
[[package]]
-category = "main"
+category = "dev"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "1.12.0"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "1.14.0"
[[package]]
category = "dev"
@@ -567,21 +798,26 @@ category = "dev"
description = "tox is a generic virtualenv management and test command line tool"
name = "tox"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.14.0"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+version = "3.14.5"
[package.dependencies]
+colorama = ">=0.4.1"
filelock = ">=3.0.0,<4"
packaging = ">=14"
pluggy = ">=0.12.0,<1"
py = ">=1.4.17,<2"
-six = ">=1.0.0,<2"
+six = ">=1.14.0,<2"
toml = ">=0.9.4"
-virtualenv = ">=14.0.0"
+virtualenv = ">=16.0.0"
[package.dependencies.importlib-metadata]
python = "<3.8"
-version = ">=0.12,<1"
+version = ">=0.12,<2"
+
+[package.extras]
+docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"]
+testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"]
[[package]]
category = "dev"
@@ -596,16 +832,19 @@ decorator = "*"
ipython-genutils = "*"
six = "*"
+[package.extras]
+test = ["pytest", "mock"]
+
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
optional = false
python-versions = "*"
-version = "1.4.0"
+version = "1.4.1"
[[package]]
-category = "main"
+category = "dev"
description = "Type Hints for Python"
name = "typing"
optional = false
@@ -617,8 +856,13 @@ category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
-version = "1.25.6"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "1.25.8"
+
+[package.extras]
+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 || >1.5.7,<2.0)"]
[[package]]
category = "dev"
@@ -626,23 +870,47 @@ description = "Virtual Python Environment builder"
name = "virtualenv"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-version = "16.7.7"
+version = "20.0.7"
+
+[package.dependencies]
+appdirs = ">=1.4.3,<2"
+distlib = ">=0.3.0,<1"
+filelock = ">=3.0.0,<4"
+six = ">=1.9.0,<2"
+
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = ">=0.12,<2"
+
+[package.dependencies.importlib-resources]
+python = "<3.7"
+version = ">=1.0,<2"
+
+[package.extras]
+docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"]
+testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "packaging (>=20.0)", "xonsh (>=0.9.13,<1)"]
[[package]]
category = "dev"
-description = "Measures number of Terminal column cells of wide-character codes"
-name = "wcwidth"
+description = "Filesystem events monitoring"
+name = "watchdog"
optional = false
python-versions = "*"
-version = "0.1.7"
+version = "0.10.2"
+
+[package.dependencies]
+pathtools = ">=0.1.1"
+
+[package.extras]
+watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"]
[[package]]
-category = "main"
-description = "Module for decorators, wrappers and monkey patching."
-name = "wrapt"
+category = "dev"
+description = "Measures number of Terminal column cells of wide-character codes"
+name = "wcwidth"
optional = false
python-versions = "*"
-version = "1.11.2"
+version = "0.1.8"
[[package]]
category = "dev"
@@ -650,78 +918,431 @@ description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version < \"3.8\""
name = "zipp"
optional = false
-python-versions = ">=2.7"
-version = "0.6.0"
+python-versions = ">=3.6"
+version = "3.1.0"
-[package.dependencies]
-more-itertools = "*"
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
+testing = ["jaraco.itertools", "func-timeout"]
[metadata]
-content-hash = "ffab655b4c5b16daddef7356957dc47ea90f4fccf89ec94c97d8b15bf02736a2"
+content-hash = "7b63b2492e128d1a8901ba281fac2efba0c7941d4309d3cd0d38dc88b10e4071"
python-versions = "^3.6"
-[metadata.hashes]
-appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"]
-appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"]
-atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
-attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"]
-backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"]
-black = ["1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", "c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"]
-certifi = ["e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"]
-chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
-click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
-colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
-coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"]
-dataclasses = ["454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f", "6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"]
-decorator = ["54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", "5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d"]
-deprecated = ["a515c4cf75061552e0284d123c3066fbbe398952c87333a92b8fc3dd8e4f9cc1", "b07b414c8aac88f60c1d837d21def7e83ba711052e03b3cbaff27972567a8f8d"]
-entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"]
-filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"]
-flake8 = ["45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", "49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"]
-idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
-importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"]
-ipython = ["dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280", "ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995"]
-ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"]
-jedi = ["786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"]
-jinja2 = ["74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"]
-livereload = ["78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b", "89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"]
-markdown = ["2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", "56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"]
-markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
-mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
-mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"]
-mkdocs-cinder = ["0623b3b8c3a70fb735e966da805ff6f33e6c547ae9b26e6146d4995053e182f5", "984ba6df20916240647e5386d8444e6dbf3a8200e4d6db2d58cb145f40f99ff2"]
-more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
-"nr.types" = ["6c44f37441cf5d563c9e0302dc2bc942b2a22d7a73ff18fa71f88017fa6254ef"]
-packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"]
-parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"]
-pathspec = ["e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c"]
-pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"]
-pickleshare = ["87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", "9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"]
-pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"]
-prompt-toolkit = ["46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4", "e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31", "f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db"]
-ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"]
-py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
-pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"]
-pydantic = ["0f35a8d780534f3db5c36fe4d516bd7a12559d6972dccbc55161a90eb20dd67a", "360433b2422fcb2203356a30a90f6ae45d9e613243588504509aa5d544d6f68e", "751f755efb9ab395ec7bd562f123e57a68e6164f06fc735b04ccb8df1ad5b6c8", "a1ba32e2284185260cf5d35ffe2c8cbdd2244d62e5952e69572d4a458d6d46a7", "bf474cebe007701806f5f8b076fb8508116606e5c721734bb855bfec4185263c", "c044e589c6d8d54ad55f5e73aa8f8d61dcab038b0a6267988d68e0361ae77f0e"]
+[metadata.files]
+appdirs = [
+ {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
+ {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
+]
+appnope = [
+ {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
+ {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
+]
+atomicwrites = [
+ {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"},
+ {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"},
+]
+attrs = [
+ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
+ {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
+]
+backcall = [
+ {file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"},
+ {file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"},
+]
+black = [
+ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
+ {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
+]
+certifi = [
+ {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
+ {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
+]
+chardet = [
+ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
+ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
+]
+click = [
+ {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"},
+ {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
+]
+colorama = [
+ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
+ {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
+]
+coverage = [
+ {file = "coverage-5.0.3-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f"},
+ {file = "coverage-5.0.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc"},
+ {file = "coverage-5.0.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a"},
+ {file = "coverage-5.0.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52"},
+ {file = "coverage-5.0.3-cp27-cp27m-win32.whl", hash = "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c"},
+ {file = "coverage-5.0.3-cp27-cp27m-win_amd64.whl", hash = "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73"},
+ {file = "coverage-5.0.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68"},
+ {file = "coverage-5.0.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691"},
+ {file = "coverage-5.0.3-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301"},
+ {file = "coverage-5.0.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf"},
+ {file = "coverage-5.0.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3"},
+ {file = "coverage-5.0.3-cp35-cp35m-win32.whl", hash = "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"},
+ {file = "coverage-5.0.3-cp35-cp35m-win_amd64.whl", hash = "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0"},
+ {file = "coverage-5.0.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2"},
+ {file = "coverage-5.0.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894"},
+ {file = "coverage-5.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf"},
+ {file = "coverage-5.0.3-cp36-cp36m-win32.whl", hash = "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477"},
+ {file = "coverage-5.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc"},
+ {file = "coverage-5.0.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8"},
+ {file = "coverage-5.0.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987"},
+ {file = "coverage-5.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea"},
+ {file = "coverage-5.0.3-cp37-cp37m-win32.whl", hash = "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc"},
+ {file = "coverage-5.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e"},
+ {file = "coverage-5.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb"},
+ {file = "coverage-5.0.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37"},
+ {file = "coverage-5.0.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d"},
+ {file = "coverage-5.0.3-cp38-cp38m-win32.whl", hash = "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954"},
+ {file = "coverage-5.0.3-cp38-cp38m-win_amd64.whl", hash = "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e"},
+ {file = "coverage-5.0.3-cp39-cp39m-win32.whl", hash = "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40"},
+ {file = "coverage-5.0.3-cp39-cp39m-win_amd64.whl", hash = "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af"},
+ {file = "coverage-5.0.3.tar.gz", hash = "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef"},
+]
+dataclasses = [
+ {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"},
+ {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"},
+]
+decorator = [
+ {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
+ {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
+]
+distlib = [
+ {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"},
+]
+entrypoints = [
+ {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
+ {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
+]
+filelock = [
+ {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
+ {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
+]
+flake8 = [
+ {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"},
+ {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"},
+]
+future = [
+ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
+]
+idna = [
+ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
+ {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
+]
+importlib-metadata = [
+ {file = "importlib_metadata-1.5.0-py2.py3-none-any.whl", hash = "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"},
+ {file = "importlib_metadata-1.5.0.tar.gz", hash = "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302"},
+]
+importlib-resources = [
+ {file = "importlib_resources-1.2.0-py2.py3-none-any.whl", hash = "sha256:6aefffdf63634bd94600dd0691fd6430902272bace0572934fa23c81473a7ff7"},
+ {file = "importlib_resources-1.2.0.tar.gz", hash = "sha256:ce84b1c9c05078e1797700505bd1bc386cee9561002a9bdcfcba634adc6333e5"},
+]
+ipython = [
+ {file = "ipython-7.13.0-py3-none-any.whl", hash = "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333"},
+ {file = "ipython-7.13.0.tar.gz", hash = "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a"},
+]
+ipython-genutils = [
+ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
+ {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
+]
+jedi = [
+ {file = "jedi-0.16.0-py2.py3-none-any.whl", hash = "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2"},
+ {file = "jedi-0.16.0.tar.gz", hash = "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"},
+]
+jinja2 = [
+ {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"},
+ {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"},
+]
+livereload = [
+ {file = "livereload-2.6.1-py2.py3-none-any.whl", hash = "sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b"},
+ {file = "livereload-2.6.1.tar.gz", hash = "sha256:89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"},
+]
+lunr = [
+ {file = "lunr-0.5.6-py2.py3-none-any.whl", hash = "sha256:1208622930c915a07e6f8e8640474357826bad48534c0f57969b6fca9bffc88e"},
+ {file = "lunr-0.5.6.tar.gz", hash = "sha256:7be69d7186f65784a4f2adf81e5c58efd6a9921aa95966babcb1f2f2ada75c20"},
+]
+markdown = [
+ {file = "Markdown-3.2.1-py2.py3-none-any.whl", hash = "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d"},
+ {file = "Markdown-3.2.1.tar.gz", hash = "sha256:90fee683eeabe1a92e149f7ba74e5ccdc81cd397bd6c516d93a8da0ef90b6902"},
+]
+markupsafe = [
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
+ {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
+ {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
+ {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
+ {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
+ {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
+ {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
+ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
+]
+mccabe = [
+ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+mkdocs = [
+ {file = "mkdocs-1.1-py2.py3-none-any.whl", hash = "sha256:1e385a70aea8a9dedb731aea4fd5f3704b2074801c4f96f06b2920999babda8a"},
+ {file = "mkdocs-1.1.tar.gz", hash = "sha256:9243291392f59e20b655e4e46210233453faf97787c2cf72176510e868143174"},
+]
+mkdocs-cinder = [
+ {file = "mkdocs-cinder-0.17.0.tar.gz", hash = "sha256:984ba6df20916240647e5386d8444e6dbf3a8200e4d6db2d58cb145f40f99ff2"},
+ {file = "mkdocs_cinder-0.17.0-py3-none-any.whl", hash = "sha256:0623b3b8c3a70fb735e966da805ff6f33e6c547ae9b26e6146d4995053e182f5"},
+]
+more-itertools = [
+ {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
+ {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
+]
+nltk = [
+ {file = "nltk-3.4.5.win32.exe", hash = "sha256:a08bdb4b8a1c13de16743068d9eb61c8c71c2e5d642e8e08205c528035843f82"},
+ {file = "nltk-3.4.5.zip", hash = "sha256:bed45551259aa2101381bbdd5df37d44ca2669c5c3dad72439fa459b29137d94"},
+]
+"nr.collections" = [
+ {file = "nr.collections-0.0.1.tar.gz", hash = "sha256:ddf38cd6379cac546ce7abdadf024fc01cca75540e11b1d5f1aa701a33817f1c"},
+]
+"nr.databind" = [
+ {file = "nr.databind-0.0.4.tar.gz", hash = "sha256:dfa01c23094c2acac2407d9a28b0cdc37aa2cb16afa2de7ca03a5b2248b3005a"},
+]
+"nr.interface" = [
+ {file = "nr.interface-0.0.1.tar.gz", hash = "sha256:115c9a1a2bfa670cddf385a1a2c07137711021f0be11def268e5d89da5b7b23f"},
+]
+"nr.metaclass" = [
+ {file = "nr.metaclass-0.0.4.tar.gz", hash = "sha256:0ac9c4c9a41994c0a000f209a52a9bb25f5efc0ad75e38d42ef1b4780ecd3cb9"},
+]
+"nr.parsing.date" = [
+ {file = "nr.parsing.date-0.0.1.tar.gz", hash = "sha256:5933d37b41bae9fa73cb9dd28d3b36385c83b3b655ddab38eeb7231f8dc5bb7a"},
+]
+"nr.pylang.utils" = [
+ {file = "nr.pylang.utils-0.0.1.tar.gz", hash = "sha256:966531c03175767c9ef3065842d8d5c7e30486dcf4a5ebd5e9ed80441da15550"},
+]
+"nr.stream" = [
+ {file = "nr.stream-0.0.2.tar.gz", hash = "sha256:67474cdda67a78c005ae3da5f26f6c4f49e2cb1bcb73d2e25f8359b630e538dc"},
+]
+packaging = [
+ {file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"},
+ {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"},
+]
+parso = [
+ {file = "parso-0.6.2-py2.py3-none-any.whl", hash = "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995"},
+ {file = "parso-0.6.2.tar.gz", hash = "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157"},
+]
+pathspec = [
+ {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"},
+ {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"},
+]
+pathtools = [
+ {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"},
+]
+pexpect = [
+ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
+ {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+]
+pickleshare = [
+ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
+ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
+]
+pluggy = [
+ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
+ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+]
+prompt-toolkit = [
+ {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"},
+ {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"},
+]
+ptyprocess = [
+ {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
+ {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
+]
+py = [
+ {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"},
+ {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"},
+]
+pycodestyle = [
+ {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"},
+ {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"},
+]
+pydantic = [
+ {file = "pydantic-1.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:07911aab70f3bc52bb845ce1748569c5e70478ac977e106a150dd9d0465ebf04"},
+ {file = "pydantic-1.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:012c422859bac2e03ab3151ea6624fecf0e249486be7eb8c6ee69c91740c6752"},
+ {file = "pydantic-1.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:61d22d36808087d3184ed6ac0d91dd71c533b66addb02e4a9930e1e30833202f"},
+ {file = "pydantic-1.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f863456d3d4bf817f2e5248553dee3974c5dc796f48e6ddb599383570f4215ac"},
+ {file = "pydantic-1.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bbbed364376f4a0aebb9ea452ff7968b306499a9e74f4db69b28ff2cd4043a11"},
+ {file = "pydantic-1.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e27559cedbd7f59d2375bfd6eea29a330ea1a5b0589c34d6b4e0d7bec6027bbf"},
+ {file = "pydantic-1.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:50e4e948892a6815649ad5a9a9379ad1e5f090f17842ac206535dfaed75c6f2f"},
+ {file = "pydantic-1.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8848b4eb458469739126e4c1a202d723dd092e087f8dbe3104371335f87ba5df"},
+ {file = "pydantic-1.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:831a0265a9e3933b3d0f04d1a81bba543bafbe4119c183ff2771871db70524ab"},
+ {file = "pydantic-1.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:47b8db7024ba3d46c3d4768535e1cf87b6c8cf92ccd81e76f4e1cb8ee47688b3"},
+ {file = "pydantic-1.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:51f11c8bbf794a68086540da099aae4a9107447c7a9d63151edbb7d50110cf21"},
+ {file = "pydantic-1.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6100d7862371115c40be55cc4b8d766a74b1d0dbaf99dbfe72bb4bac0faf89ed"},
+ {file = "pydantic-1.4-py36.py37.py38-none-any.whl", hash = "sha256:72184c1421103cca128300120f8f1185fb42a9ea73a1c9845b1c53db8c026a7d"},
+ {file = "pydantic-1.4.tar.gz", hash = "sha256:f17ec336e64d4583311249fb179528e9a2c27c8a2eaf590ec6ec2c6dece7cb3f"},
+]
pydoc-markdown = []
-pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"]
-pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"]
-pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"]
-pytest = ["27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", "58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4"]
-pytest-cov = ["cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", "cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"]
-pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"]
-regex = ["15454b37c5a278f46f7aa2d9339bda450c300617ca2fca6558d05d870245edc7", "1ad40708c255943a227e778b022c6497c129ad614bb7a2a2f916e12e8a359ee7", "5e00f65cc507d13ab4dfa92c1232d004fa202c1d43a32a13940ab8a5afe2fb96", "604dc563a02a74d70ae1f55208ddc9bfb6d9f470f6d1a5054c4bd5ae58744ab1", "720e34a539a76a1fedcebe4397290604cc2bdf6f81eca44adb9fb2ea071c0c69", "7caf47e4a9ac6ef08cabd3442cc4ca3386db141fb3c8b2a7e202d0470028e910", "7faf534c1841c09d8fefa60ccde7b9903c9b528853ecf41628689793290ca143", "b4e0406d822aa4993ac45072a584d57aa4931cf8288b5455bbf30c1d59dbad59", "c31eaf28c6fe75ea329add0022efeed249e37861c19681960f99bbc7db981fb2", "c7393597191fc2043c744db021643549061e12abe0b3ff5c429d806de7b93b66", "d2b302f8cdd82c8f48e9de749d1d17f85ce9a0f082880b9a4859f66b07037dc6", "e3d8dd0ec0ea280cf89026b0898971f5750a7bd92cb62c51af5a52abd020054a", "ec032cbfed59bd5a4b8eab943c310acfaaa81394e14f44454ad5c9eba4f24a74"]
-requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
-requests-mock = ["510df890afe08d36eca5bb16b4aa6308a6f85e3159ad3013bac8b9de7bd5a010", "88d3402dd8b3c69a9e4f9d3a73ad11b15920c6efd36bc27bf1f701cf4a8e4646"]
-six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
-toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
-tornado = ["349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", "398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", "4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", "559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", "abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", "c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", "c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"]
-tox = ["0bc216b6a2e6afe764476b4a07edf2c1dab99ed82bb146a1130b2e828f5bff5e", "c4f6b319c20ba4913dbfe71ebfd14ff95d1853c4231493608182f66e566ecfe1"]
-traitlets = ["70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", "d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"]
-typed-ast = ["18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"]
-typing = ["91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", "c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", "f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"]
-urllib3 = ["3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", "9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"]
-virtualenv = ["11cb4608930d5fd3afb545ecf8db83fa50e1f96fc4fca80c94b07d2c83146589", "d257bb3773e48cac60e475a19b608996c73f4d333b3ba2e4e57d5ac6134e0136"]
-wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
-wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"]
-zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"]
+pyflakes = [
+ {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"},
+ {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"},
+]
+pygments = [
+ {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},
+ {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"},
+]
+pyparsing = [
+ {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
+ {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
+]
+pytest = [
+ {file = "pytest-5.3.5-py3-none-any.whl", hash = "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6"},
+ {file = "pytest-5.3.5.tar.gz", hash = "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d"},
+]
+pytest-cov = [
+ {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"},
+ {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"},
+]
+python-dateutil = [
+ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
+ {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
+]
+pyyaml = [
+ {file = "PyYAML-5.3-cp27-cp27m-win32.whl", hash = "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d"},
+ {file = "PyYAML-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6"},
+ {file = "PyYAML-5.3-cp35-cp35m-win32.whl", hash = "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e"},
+ {file = "PyYAML-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689"},
+ {file = "PyYAML-5.3-cp36-cp36m-win32.whl", hash = "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994"},
+ {file = "PyYAML-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e"},
+ {file = "PyYAML-5.3-cp37-cp37m-win32.whl", hash = "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5"},
+ {file = "PyYAML-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf"},
+ {file = "PyYAML-5.3-cp38-cp38-win32.whl", hash = "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811"},
+ {file = "PyYAML-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20"},
+ {file = "PyYAML-5.3.tar.gz", hash = "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"},
+]
+regex = [
+ {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"},
+ {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"},
+ {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"},
+ {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"},
+ {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"},
+ {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"},
+ {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"},
+ {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"},
+ {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"},
+ {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"},
+ {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"},
+ {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"},
+ {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"},
+ {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"},
+ {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"},
+ {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"},
+ {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"},
+ {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"},
+ {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"},
+ {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"},
+ {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"},
+]
+requests = [
+ {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
+ {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
+]
+requests-mock = [
+ {file = "requests-mock-1.7.0.tar.gz", hash = "sha256:88d3402dd8b3c69a9e4f9d3a73ad11b15920c6efd36bc27bf1f701cf4a8e4646"},
+ {file = "requests_mock-1.7.0-py2.py3-none-any.whl", hash = "sha256:510df890afe08d36eca5bb16b4aa6308a6f85e3159ad3013bac8b9de7bd5a010"},
+]
+six = [
+ {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
+ {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
+]
+toml = [
+ {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
+ {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
+ {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
+]
+tornado = [
+ {file = "tornado-6.0.3-cp35-cp35m-win32.whl", hash = "sha256:c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"},
+ {file = "tornado-6.0.3-cp35-cp35m-win_amd64.whl", hash = "sha256:398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60"},
+ {file = "tornado-6.0.3-cp36-cp36m-win32.whl", hash = "sha256:4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281"},
+ {file = "tornado-6.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c"},
+ {file = "tornado-6.0.3-cp37-cp37m-win32.whl", hash = "sha256:559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5"},
+ {file = "tornado-6.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7"},
+ {file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"},
+]
+tox = [
+ {file = "tox-3.14.5-py2.py3-none-any.whl", hash = "sha256:0cbe98369081fa16bd6f1163d3d0b2a62afa29d402ccfad2bd09fb2668be0956"},
+ {file = "tox-3.14.5.tar.gz", hash = "sha256:676f1e3e7de245ad870f956436b84ea226210587d1f72c8dfb8cd5ac7b6f0e70"},
+]
+traitlets = [
+ {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
+ {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
+]
+typed-ast = [
+ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
+ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
+ {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
+ {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
+ {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
+ {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
+ {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
+ {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
+ {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
+ {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
+ {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
+ {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
+ {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
+]
+typing = [
+ {file = "typing-3.7.4.1-py2-none-any.whl", hash = "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36"},
+ {file = "typing-3.7.4.1-py3-none-any.whl", hash = "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"},
+ {file = "typing-3.7.4.1.tar.gz", hash = "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23"},
+]
+urllib3 = [
+ {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
+ {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
+]
+virtualenv = [
+ {file = "virtualenv-20.0.7-py2.py3-none-any.whl", hash = "sha256:30ea90b21dabd11da5f509710ad3be2ae47d40ccbc717dfdd2efe4367c10f598"},
+ {file = "virtualenv-20.0.7.tar.gz", hash = "sha256:4a36a96d785428278edd389d9c36d763c5755844beb7509279194647b1ef47f1"},
+]
+watchdog = [
+ {file = "watchdog-0.10.2.tar.gz", hash = "sha256:c560efb643faed5ef28784b2245cf8874f939569717a4a12826a173ac644456b"},
+]
+wcwidth = [
+ {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"},
+ {file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"},
+]
+zipp = [
+ {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
+ {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
+]
diff --git a/pyproject.toml b/pyproject.toml
index 377735f..d96a14e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,16 +1,17 @@
[tool.poetry]
name = "gns3fy"
-version = "0.6.0"
+version = "0.7.0"
description = "Python wrapper around GNS3 Server API"
authors = ["David Flores "]
license = "MIT"
readme = "README.md"
repository = "https://github.com/davidban77/gns3fy"
homepage = "https://github.com/davidban77/gns3fy"
-keywords = ["network", "gns3", "python", "restapi"]
+keywords = ["network", "gns3", "python", "restapi", "netdev"]
classifiers = [
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.8",
]
[tool.poetry.dependencies]
@@ -22,14 +23,14 @@ pydantic = "^1.0"
ipython = "^7.7"
pytest = "^5.0"
flake8 = "^3.7"
-black = {version = "^19.10b0",allows-prereleases = true}
+black = {version = "^19.10b0", allow-prereleases = true}
requests-mock = "^1.6"
pytest-cov = "^2.7"
mkdocs = "^1.0"
-pydoc-markdown = { git = "https://github.com/NiklasRosenstein/pydoc-markdown.git", branch = "develop", category="dev" }
+pydoc-markdown = {git = "https://github.com/NiklasRosenstein/pydoc-markdown.git", branch = "develop"}
mkdocs-cinder = "^0.17.0"
tox = "^3.13"
[build-system]
-requires = ["poetry>=0.12"]
+requires = ["poetry>=1.0"]
build-backend = "poetry.masonry.api"
diff --git a/tests/test_drawing_utils.py b/tests/test_drawing_utils.py
new file mode 100644
index 0000000..182b6f9
--- /dev/null
+++ b/tests/test_drawing_utils.py
@@ -0,0 +1,42 @@
+from gns3fy.drawing_utils import (
+ generate_ellipse_svg,
+ generate_line_svg,
+ generate_rectangle_svg,
+ parsed_x,
+ parsed_y,
+)
+
+
+def test_generate_rectangle_svg():
+ rectangle = generate_rectangle_svg()
+ assert rectangle == (
+ ''
+ )
+
+
+def test_generate_ellipse_svg():
+ rectangle = generate_ellipse_svg()
+ assert rectangle == (
+ '"
+ )
+
+
+def test_generate_line_svg():
+ rectangle = generate_line_svg()
+ assert rectangle == (
+ ''
+ )
+
+
+def test_parsed_x():
+ x_value = parsed_x(x=7)
+ assert x_value == 700
+
+
+def test_parsed_y():
+ y_value = parsed_y(y=7)
+ assert y_value == -700
diff --git a/tests/test_api.py b/tests/test_gns3fy.py
similarity index 93%
rename from tests/test_api.py
rename to tests/test_gns3fy.py
index be6cc41..ae0768a 100644
--- a/tests/test_api.py
+++ b/tests/test_gns3fy.py
@@ -14,6 +14,7 @@
CPROJECT = {"name": "API_TEST", "id": "4b21dfb3-675a-4efa-8613-2f7fb32e76fe"}
CNODE = {"name": "alpine-1", "id": "ef503c45-e998-499d-88fc-2765614b313e"}
CTEMPLATE = {"name": "alpine", "id": "847e5333-6ac9-411f-a400-89838584371b"}
+CDRAWING = {"id": "04e326ab-09fa-47e6-957e-d5a285efb988"}
CLINK = {"link_type": "ethernet", "id": "4d9f1235-7fd1-466b-ad26-0b4b08beb778"}
CCOMPUTE = {"id": "local"}
CIMAGE = {"filename": "vEOS-lab-4.21.5F.vmdk"}
@@ -114,7 +115,10 @@ def post_put_matcher(request):
"Creates the Responses for POST and PUT requests"
resp = requests.Response()
if request.method == "POST":
- if request.path_url.endswith("/projects"):
+ if request.path_url.endswith("/computes/local/qemu/images/files.txt"):
+ resp.status_code = 204
+ return resp
+ elif request.path_url.endswith("/projects"):
# Now verify the data
_data = request.json()
if _data["name"] == "API_TEST":
@@ -128,10 +132,7 @@ def post_put_matcher(request):
)
return resp
elif request.path_url.endswith(f"/{CPROJECT['id']}/close"):
- _returned = json_api_test_project()
- _returned.update(status="closed")
resp.status_code = 204
- resp.json = lambda: _returned
return resp
elif request.path_url.endswith(f"/{CPROJECT['id']}/open"):
_returned = json_api_test_project()
@@ -162,6 +163,21 @@ def post_put_matcher(request):
resp.json = lambda: _returned
resp.status_code = 201
return resp
+ elif request.path_url.endswith(f"/{CPROJECT['id']}/drawings"):
+ _data = request.json()
+ _returned = dict(
+ locked=False,
+ rotation=0,
+ x=10,
+ y=20,
+ z=0,
+ svg=_data.get("svg"),
+ project_id="28ea5feb-c006-4724-80ec-a7cc0d8b8a5a",
+ drawing_id="62afa856-4a43-4444-a376-60f6f963bb3d",
+ )
+ resp.json = lambda: _returned
+ resp.status_code = 201
+ return resp
elif request.path_url.endswith(
f"/{CPROJECT['id']}/snapshots/44e08d78-0ee4-4b8f-bad4-117aa67cb759/restore"
):
@@ -298,6 +314,12 @@ def post_put_matcher(request):
resp.status_code = 200
resp.json = lambda: _data
return resp
+ elif request.path_url.endswith(f"/drawings/{CDRAWING['id']}"):
+ _data = request.json()
+ if _data["x"] == -256:
+ resp.status_code = 201
+ resp.json = lambda: _data
+ return resp
return None
@@ -370,7 +392,7 @@ def _apply_responses(self):
self.adapter.register_uri(
"GET",
f"{self.base_url}/projects/{CPROJECT['id']}/stats",
- json={"drawings": 0, "links": 4, "nodes": 6, "snapshots": 2},
+ json={"drawings": 2, "links": 4, "nodes": 6, "snapshots": 2},
)
# Get a project README file info
self.adapter.register_uri(
@@ -409,6 +431,11 @@ def _apply_responses(self):
json=projects_drawings_data(),
status_code=200,
)
+ self.adapter.register_uri(
+ "DELETE",
+ f"{self.base_url}/projects/{CPROJECT['id']}/drawings/{CDRAWING['id']}",
+ status_code=204,
+ )
# Extra project
self.adapter.register_uri(
"GET",
@@ -831,6 +858,18 @@ def test_get_compute_qemu_images(self, gns3_server):
assert n[0] == response[index]["filename"]
assert n[1] == response[index]["filesize"]
+ def test_upload_compute_image_not_found(self, gns3_server):
+ # NOTE: This is better tested on an integration scenario
+ with pytest.raises(FileNotFoundError, match="Could not find file: dummy"):
+ gns3_server.upload_compute_image(emulator="qemu", file_path="dummy")
+
+ def test_upload_compute_image(self, gns3_server):
+ # NOTE: This is better tested on an integration scenario
+ response = gns3_server.upload_compute_image(
+ emulator="qemu", file_path=DATA_FILES / "files.txt"
+ )
+ assert response is None
+
def test_get_compute_ports(self, gns3_server):
response = gns3_server.get_compute_ports(compute_id="local")
assert response["console_port_range"] == [5000, 10000]
@@ -1174,7 +1213,7 @@ def test_get(self, api_test_project):
assert "API_TEST" == api_test_project.name
assert "opened" == api_test_project.status
assert {
- "drawings": 0,
+ "drawings": 2,
"links": 4,
"nodes": 6,
"snapshots": 2,
@@ -1223,7 +1262,7 @@ def test_close(self, api_test_project):
def test_get_stats(self, api_test_project):
api_test_project.get_stats()
assert {
- "drawings": 0,
+ "drawings": 2,
"links": 4,
"nodes": 6,
"snapshots": 2,
@@ -1489,8 +1528,57 @@ def test_error_restore_snapshot_not_found(self, api_test_project):
def test_get_drawings(self, api_test_project):
api_test_project.get_drawings()
assert isinstance(api_test_project.drawings, list)
- assert (
- api_test_project.drawings[0]["drawing_id"]
- == "04e326ab-09fa-47e6-957e-d5a285efb988"
- )
+ assert api_test_project.drawings[0]["drawing_id"] == CDRAWING["id"]
assert api_test_project.drawings[0]["project_id"] == api_test_project.project_id
+
+ def test_error_get_drawing_not_found(self, api_test_project):
+ dummy = api_test_project.get_drawing(drawing_id="dummy")
+ assert dummy is None
+
+ def test_get_drawing(self, api_test_project):
+ api_test_project.drawings = None
+ drawing = api_test_project.get_drawing(drawing_id=CDRAWING["id"])
+ assert drawing["svg"] == (
+ ''
+ )
+ assert drawing["x"] == -256
+ assert drawing["y"] == -237
+ assert drawing["z"] == 1
+
+ def test_create_drawing(self, api_test_project):
+ api_test_project.create_drawing(
+ svg=(
+ ''
+ ),
+ x=10,
+ y=20,
+ z=0,
+ )
+ drawing = api_test_project.drawings[-1]
+ assert drawing["drawing_id"] == "62afa856-4a43-4444-a376-60f6f963bb3d"
+ assert drawing["project_id"] == "28ea5feb-c006-4724-80ec-a7cc0d8b8a5a"
+ assert drawing["x"] == 10
+ assert drawing["y"] == 20
+ assert drawing["z"] == 0
+
+ def test_update_drawing(self, api_test_project):
+ api_test_project.get_drawings()
+
+ # Update:
+ # NOTE: This should be really well tested in an integration phase since
+ # the values are not really altered here
+ api_test_project.update_drawing(
+ drawing_id=api_test_project.drawings[0]["drawing_id"]
+ )
+ assert api_test_project.drawings[0]["x"] == -256
+ assert api_test_project.drawings[0]["drawing_id"] == CDRAWING["id"]
+
+ def test_delete_drawing(self, api_test_project):
+ response = api_test_project.delete_drawing(drawing_id=CDRAWING["id"])
+ assert response is None
+
+ def test_delete_drawing_not_found(self, api_test_project):
+ with pytest.raises(ValueError, match="drawing not found"):
+ api_test_project.delete_drawing(drawing_id="dummmy")
diff --git a/tox.ini b/tox.ini
index 09c3d1f..ede3ae8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,8 +2,9 @@
max-line-length=88
[tox]
+requires = tox-conda
isolated_build = true
-envlist = python3.6, python3.7
+envlist = py36, py37, py38
[testenv]
whitelist_externals = poetry