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