diff --git a/.doit.db b/.doit.db new file mode 100644 index 0000000..c789bd1 Binary files /dev/null and b/.doit.db differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e452b42 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: +- repo: local + hooks: + - id: check + name: check + entry: doit check + language: system + types: [python] + pass_filenames: false \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..966c017 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +# .readthedocs.yaml +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3.12" + jobs: + post_create_environment: + # Install poetry + # https://python-poetry.org/docs/#installing-manually + - python -m pip install poetry + post_install: + # Install dependencies with 'docs' dependency group + # https://python-poetry.org/docs/managing-dependencies/#dependency-groups + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..1626b29 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,52 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: >- + Architecture of an Artificial Intelligence Model + Manager for Event-Driven Component-Based SCADA + Systems +message: To cite AIMM please use the following parameters +type: software +authors: + - given-names: Zlatan Sičanica + email: zlatan.sicanica@koncar.hr + affiliation: Končar — Digital + orcid: 'https://orcid.org/0000-0001-9731-3000' + - given-names: Stjepan Sučić + affiliation: Končar — Digital + - given-names: Boris Milašinović + affiliation: >- + Faculty of Electrical Engineering and + Computing, University of Zagreb + orcid: 'https://orcid.org/0000-0002-7889-3131' +identifiers: + - type: doi + value: 10.1109/ACCESS.2022.3159715 + - type: url + value: 'https://aimm.readthedocs.io/en/latest/' +repository-code: 'https://github.com/hat-open/aimm' +abstract: >- + This paper analyzes Hat, an open-source framework + for developing event-driven component-based SCADA + applications, and discusses possibilities to add + various analytical tools to such platforms. As a + part of the contribution, an open-source component + called Artificial Intelligence Model Manager (AIMM) + has been developed and integrated into a Hat-based + SCADA platform. AIMM is extensible through various + plugins, allowing the addition of various models + for advanced analytics e.g., machine learning + tools, statistical tools, etc. The paper describes + AIMM architecture and provides a use case in which + state estimation was performed in a medium-voltage + distribution grid. This case study demonstrates + that it is possible to extend component-based SCADA + systems with components for advanced analytics with + minimal fundamental system changes. +keywords: + - Artificial intelligence + - Power system analysis compouting + - SCADA systems + - Software architecture +license: Apache-2.0 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..ebeca4c --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,18 @@ +Contributing +============ + +Welcome to the contributing guidelines for the AIMM project. This document is +intended for new contributors, to get acquainted with the contribution +procedures. + +This project is in early stages of development, with no exact functional +specification (i.e., the current specification might be subject to changes). +For this reason, pull requests are discouraged at this moment. + +Bug reports may be created using GitHub's issue tracker. However, the same +warning as above applies, the issues might be declined. + +The issue tracker may also be used to ask questions about the project or +suggest changes/new features. We will look into these and add them to our +milestones, if accepted. Feel free to create a suggestion issue. The discussion +is currently imagined to be in free-form, not enforcing any templates. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..473d8eb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.10 + +WORKDIR /opt/aimm + +RUN pip install aimm + +ENV PYTHONPATH=/opt/aimm + +CMD ["aimm-server", "--conf", "aimm.yaml"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..0caf47e --- /dev/null +++ b/README.rst @@ -0,0 +1,43 @@ +Artificial Intelligence Model Manager +===================================== + +The Artificial Intelligence Model Manager (AIMM) project aims to provide +resources for management of computational intelligence models. Using a +plugin-based approach, it provides a services capable of: + + * creating and storing models + * fitting models + * upload of already fitted models + * data access + * running the models + +The server also has support for changeable frontend and persistence interfaces. +This allows users to implement the ways server communicates to its clients +(multiple parallel interfaces are supported) or stores the models. There are +also default interfaces that are supported for both of these functions. + +Installation +------------ + +AIMM is a Python (3.12 and newer) package containing implementations of the +server implementation and some of its clients. It can be installed with the +following command:: + + pip install aimm + +Development environment +----------------------- + +Development environment includes, besides the standard requirements of the base +AIMM package, various tools and libraries that are used for the build process, +documentation and testing. To set up the development environment, Python 3.12 +and poetry are needed. Recommended way to set up is by running:: + + python -m venv venv + source venv/bin/activate + pip install poetry + poetry install + +All other generic tasks like testing and documentation building are done +through the build tool, use ``doit list`` to preview the complete list of all +available tasks. diff --git a/__pycache__/dodo.cpython-312.pyc b/__pycache__/dodo.cpython-312.pyc new file mode 100644 index 0000000..62c9207 Binary files /dev/null and b/__pycache__/dodo.cpython-312.pyc differ diff --git a/aimm/__init__.py b/aimm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aimm/__pycache__/__init__.cpython-312.pyc b/aimm/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..f0cfe86 Binary files /dev/null and b/aimm/__pycache__/__init__.cpython-312.pyc differ diff --git a/aimm/__pycache__/common.cpython-312.pyc b/aimm/__pycache__/common.cpython-312.pyc new file mode 100644 index 0000000..075427d Binary files /dev/null and b/aimm/__pycache__/common.cpython-312.pyc differ diff --git a/aimm/client/__init__.py b/aimm/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aimm/client/__pycache__/__init__.cpython-312.pyc b/aimm/client/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..aaa0a7c Binary files /dev/null and b/aimm/client/__pycache__/__init__.cpython-312.pyc differ diff --git a/aimm/client/__pycache__/repl.cpython-312.pyc b/aimm/client/__pycache__/repl.cpython-312.pyc new file mode 100644 index 0000000..44a1cf6 Binary files /dev/null and b/aimm/client/__pycache__/repl.cpython-312.pyc differ diff --git a/aimm/client/repl.py b/aimm/client/repl.py new file mode 100644 index 0000000..18c014f --- /dev/null +++ b/aimm/client/repl.py @@ -0,0 +1,242 @@ +"""REPL client module. Provides a minimal interface that follows the protocol +specified by the REPL control.""" + +from getpass import getpass + +from hat import aio +from hat import juggler +from tenacity import AsyncRetrying, stop_after_attempt, wait_fixed +import base64 +import hashlib +import numpy +import numpy.typing +import pandas +import typing + +from aimm import plugins +from aimm.common import JSON + + +class AIMM(aio.Resource): + """Class that manages connections to AIMM REPL control, directly maps + available functions to its methods + """ + + def __init__(self): + self._address = None + self._group = aio.Group() + self._connection = None + self._state = None + + @property + def async_group(self) -> aio.Group: + """Async group""" + return self._group + + @property + def address(self) -> typing.Optional[str]: + """Current address object is connected to""" + return self._address + + @property + def state(self) -> "JSON": + """Current state reported from the AIMM server""" + return self._state + + async def connect(self, address: str): + """Connects to the specified remote address. Login data is received + from a user prompt. Passwords are hashed with SHA-256 before sending + login request.""" + self._address = address + + username = input("Username: ") + password_hash = hashlib.sha256() + password_hash.update(getpass("Password: ").encode("utf-8")) + + connection = None + async for attempt in AsyncRetrying( + wait=wait_fixed(1), stop=stop_after_attempt(3) + ): + with attempt: + connection = await juggler.connect(address) + + await connection.send( + "login", + {"username": username, "password": password_hash.hexdigest()}, + ) + self._connection = connection + + self._on_remote_state_change(connection.state.data) + self._group.spawn(connection.wait_closed).add_done_callback( + lambda _: self._clear_connection() + ) + self._connection.state.register_change_cb(self._on_remote_state_change) + + async def create_instance( + self, model_type: str, *args: "PluginArg", **kwargs: "PluginArg" + ) -> "Model": + """Creates a model instance on the remote server""" + args = tuple(_arg_to_json(a) for a in args) + kwargs = {k: _arg_to_json(v) for k, v in kwargs.items()} + model_json = await self._connection.send( + "create_instance", + {"model_type": model_type, "args": args, "kwargs": kwargs}, + ) + return Model(self, model_json["instance_id"], model_json["model_type"]) + + async def add_instance( + self, model_type: str, instance: typing.Any + ) -> "Model": + """Adds an existing instance on the remote server""" + model_json = await self._connection.send( + "add_instance", + { + "model_type": model_type, + "instance_b64": _instance_to_b64(instance, model_type), + }, + ) + return Model(self, model_json["instance_id"], model_json["model_type"]) + + async def update_instance( + self, model_type: str, instance_id: int, instance: typing.Any + ) -> "Model": + """Replaces an existing instance with a new one""" + model_json = await self._connection.send( + "update_instance", + { + "model_type": model_type, + "instance_id": instance_id, + "instance_b64": _instance_to_b64(instance, model_type), + }, + ) + return Model(self, model_json["instance_id"], model_json["model_type"]) + + async def fit( + self, instance_id: int, *args: "PluginArg", **kwargs: "PluginArg" + ) -> "Model": + """Fits an instance on the remote server""" + args = tuple(_arg_to_json(a) for a in args) + kwargs = {k: _arg_to_json(v) for k, v in kwargs.items()} + model_json = await self._connection.send( + "fit", {"instance_id": instance_id, "args": args, "kwargs": kwargs} + ) + return Model(self, model_json["instance_id"], model_json["model_type"]) + + async def predict( + self, instance_id: int, *args: "PluginArg", **kwargs: "PluginArg" + ) -> typing.Any: + """Uses an instance on the remote server for a prediction""" + args = tuple(_arg_to_json(a) for a in args) + kwargs = {k: _arg_to_json(v) for k, v in kwargs.items()} + result = await self._connection.send( + "predict", + {"instance_id": instance_id, "args": args, "kwargs": kwargs}, + ) + return _result_from_json(result) + + def _clear_connection(self): + if self._connection: + self._connection.close() + self._connection = None + + def _on_remote_state_change(self, remote_state): + if remote_state is None: + return + self._state = { + "models": { + int(k): Model(self, k, v["model_type"]) + for k, v in remote_state["models"].items() + }, + "actions": {int(k): v for k, v in remote_state["actions"].items()}, + } + + +class Model: + """Represents an AIMM model instance and provides a simplified interface + for using or changing it remotely.""" + + def __init__(self, aimm: AIMM, instance_id: int, model_type: str): + self._aimm = aimm + self._instance_id = instance_id + self._model_type = model_type + + async def fit(self, *args: "PluginArg", **kwargs: "PluginArg"): + """Fits the model""" + await self._aimm.fit(self._instance_id, *args, **kwargs) + + async def predict( + self, *args: "PluginArg", **kwargs: "PluginArg" + ) -> typing.Any: + """Uses the model to generate a prediction""" + return await self._aimm.predict(self._instance_id, *args, **kwargs) + + def __repr__(self): + return ( + f"aimm.client.repl.Model<{self._model_type}>" + f"(instance_id={self._instance_id})" + ) + + +class DataAccessArg(typing.NamedTuple): + """If passed as an argument, remote server calls a data access plugin and + passes its result instead of this object""" + + name: str + """name of the remote data access plugin""" + args: typing.List["PluginArg"] = [] + """positional arguments for the data access plugin call""" + kwargs: typing.Dict[str, "PluginArg"] = {} + """keyword arguments for the data access plugin call""" + + +PluginArg = typing.Union[ + "DataAccessArg", + numpy.typing.ArrayLike, + pandas.DataFrame, + pandas.Series, + JSON, +] +"""Represents a generic, plugin-specific argument""" + + +def _arg_to_json(arg): + if isinstance(arg, DataAccessArg): + return { + "type": "data_access", + "name": arg.name, + "args": arg.args, + "kwargs": arg.kwargs, + } + if isinstance(arg, numpy.ndarray): + return { + "type": "numpy_array", + "dtype": str(arg.dtype), + "data": arg.tolist(), + } + if isinstance(arg, pandas.DataFrame): + return {"type": "pandas_dataframe", "data": arg.to_dict()} + if isinstance(arg, pandas.Series): + return {"type": "pandas_series", "data": arg.tolist()} + return arg + + +def _result_from_json(v): + if not isinstance(v, dict): + return v + if v.get("type") == "numpy_array": + return numpy.array(v["data"]) + if v.get("type") == "pandas_dataframe": + return pandas.DataFrame.from_dict(v["data"]) + if v.get("type") == "pandas_series": + return pandas.Series(v["data"]) + return v + + +def _instance_to_b64(instance, model_type): + return base64.b64encode( + plugins.exec_serialize(model_type, instance) + ).decode("utf-8") + + +def _instance_from_b64(instance_b64, model_type): + return base64.b64decode(plugins.exec_deserialize(model_type, instance_b64)) diff --git a/aimm/common.py b/aimm/common.py new file mode 100644 index 0000000..4d49de9 --- /dev/null +++ b/aimm/common.py @@ -0,0 +1,24 @@ +from hat import json +from pathlib import Path +import hat.monitor.common +import logging +import typing + + +mlog = logging.getLogger(__name__) + + +package_path: Path = Path(__file__).parent +"""Package file system path""" + +json_schema_repo: json.SchemaRepository = json.SchemaRepository( + hat.monitor.common.json_schema_repo, + json.SchemaRepository.from_json(package_path / "json_schema_repo.json"), +) +"""JSON schema repository""" + + +JSON = typing.Union[ + None, bool, int, float, str, typing.List["JSON"], typing.Dict[str, "JSON"] +] +"""JSON serializable data""" diff --git a/aimm/json_schema_repo.json b/aimm/json_schema_repo.json new file mode 100644 index 0000000..439c8d1 --- /dev/null +++ b/aimm/json_schema_repo.json @@ -0,0 +1,303 @@ +{ + "aimm": { + "plugins.yaml": { + "id": "aimm://plugins.yaml#", + "type": "object", + "required": [ + "names" + ], + "properties": { + "names": { + "type": "array", + "items": { + "type": "string", + "description": "Python module name" + } + } + } + }, + "server/hat.yaml": { + "id": "aimm://server/hat.yaml#", + "type": "object", + "oneOf": [ + { + "required": [ + "monitor_component" + ], + "properties": { + "monitor_component": { + "type": "object", + "required": [ + "host", + "port", + "group" + ], + "properties": { + "host": { + "type": "string", + "default": "127.0.0.1" + }, + "port": { + "type": "integer", + "default": 23010 + }, + "group": { + "type": "string" + }, + "event_server_group": { + "type": "string" + } + } + } + } + }, + { + "required": [ + "eventer_server" + ], + "properties": { + "eventer_server": { + "type": "object", + "required": [ + "host", + "port" + ], + "properties": { + "host": { + "type": "string", + "default": "127.0.0.1" + }, + "port": { + "type": "integer", + "default": 23012 + } + } + } + } + } + ] + }, + "server/engine.yaml": { + "id": "aimm://server/engine.yaml#", + "type": "object", + "required": [ + "sigterm_timeout", + "max_children", + "check_children_period" + ], + "properties": { + "sigterm_timeout": { + "type": "number" + }, + "max_children": { + "type": "number" + }, + "check_children_period": { + "type": "number" + } + } + }, + "server/main.yaml": { + "id": "aimm://server/main.yaml#", + "type": "object", + "required": [ + "name", + "engine", + "backend", + "control", + "plugins", + "log" + ], + "properties": { + "name": { + "type": "string" + }, + "engine": { + "$ref": "aimm://server/engine.yaml#" + }, + "backend": { + "$ref": "aimm://server/backend/main.yaml#" + }, + "control": { + "$ref": "aimm://server/control/main.yaml#" + }, + "plugins": { + "$ref": "aimm://plugins.yaml#" + }, + "hat": { + "$ref": "aimm://server/hat.yaml#" + }, + "log": { + "type": "object" + } + } + }, + "server/backend/event.yaml": { + "id": "aimm://server/backend/event.yaml#", + "type": "object", + "required": [ + "model_prefix" + ], + "properties": { + "model_prefix": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "server/backend/main.yaml": { + "id": "aimm://server/backend/main.yaml#", + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "type": "string" + } + } + }, + "server/backend/sqlite.yaml": { + "id": "aimm://server/backend/sqlite.yaml#", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + } + } + }, + "server/control/repl.yaml": { + "id": "aimm://server/control/repl.yaml#", + "type": "object", + "required": [ + "server", + "users" + ], + "properties": { + "type": { + "const": "repl" + }, + "server": { + "type": "object", + "required": [ + "host", + "port" + ], + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + } + } + }, + "users": { + "type": "array", + "items": { + "type": "object", + "required": [ + "username", + "password" + ], + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + } + }, + "server/control/event.yaml": { + "id": "aimm://server/control/event.yaml#", + "type": "object", + "required": [ + "event_prefixes", + "state_event_type", + "action_state_event_type" + ], + "properties": { + "type": { + "const": "event" + }, + "event_prefixes": { + "type": "object", + "properties": { + "create_instance": { + "type": "array", + "items": { + "type": "string" + } + }, + "add_instance": { + "type": "array", + "items": { + "type": "string" + } + }, + "update_instance": { + "type": "array", + "items": { + "type": "string" + } + }, + "fit": { + "type": "array", + "items": { + "type": "string" + } + }, + "predict": { + "type": "array", + "items": { + "type": "string" + } + }, + "cancel": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "state_event_type": { + "type": "array", + "items": { + "type": "string" + } + }, + "action_state_event_type": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "server/control/main.yaml": { + "id": "aimm://server/control/main.yaml#", + "type": "array", + "items": { + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/aimm/plugins/__init__.py b/aimm/plugins/__init__.py new file mode 100644 index 0000000..366cafc --- /dev/null +++ b/aimm/plugins/__init__.py @@ -0,0 +1,40 @@ +from aimm.plugins.common import Model, initialize, StateCallback +from aimm.plugins.decorators import ( + data_access, + instantiate, + fit, + predict, + serialize, + deserialize, + model, + unload_all, +) +from aimm.plugins.execute import ( + exec_data_access, + exec_instantiate, + exec_fit, + exec_predict, + exec_serialize, + exec_deserialize, +) + + +__all__ = [ + "Model", + "initialize", + "exec_data_access", + "exec_instantiate", + "exec_fit", + "exec_predict", + "exec_serialize", + "exec_deserialize", + "StateCallback", + "data_access", + "instantiate", + "fit", + "predict", + "serialize", + "deserialize", + "model", + "unload_all", +] diff --git a/aimm/plugins/__pycache__/__init__.cpython-312.pyc b/aimm/plugins/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..e2e3a5b Binary files /dev/null and b/aimm/plugins/__pycache__/__init__.cpython-312.pyc differ diff --git a/aimm/plugins/__pycache__/common.cpython-312.pyc b/aimm/plugins/__pycache__/common.cpython-312.pyc new file mode 100644 index 0000000..32e0cd7 Binary files /dev/null and b/aimm/plugins/__pycache__/common.cpython-312.pyc differ diff --git a/aimm/plugins/__pycache__/decorators.cpython-312.pyc b/aimm/plugins/__pycache__/decorators.cpython-312.pyc new file mode 100644 index 0000000..6e9c48d Binary files /dev/null and b/aimm/plugins/__pycache__/decorators.cpython-312.pyc differ diff --git a/aimm/plugins/__pycache__/execute.cpython-312.pyc b/aimm/plugins/__pycache__/execute.cpython-312.pyc new file mode 100644 index 0000000..8400b55 Binary files /dev/null and b/aimm/plugins/__pycache__/execute.cpython-312.pyc differ diff --git a/aimm/plugins/common.py b/aimm/plugins/common.py new file mode 100644 index 0000000..e3f061b --- /dev/null +++ b/aimm/plugins/common.py @@ -0,0 +1,102 @@ +from typing import Any, ByteString, Callable, Dict, NamedTuple, Optional +import abc +import importlib +import logging + + +mlog = logging.getLogger(__name__) + + +class Model(abc.ABC): + """Interface unifying multiple plugin entry points under same type. + ``__init__`` method is treated as instantiation function.""" + + @abc.abstractmethod + def fit(self, *args: Any, **kwargs: Any) -> Any: + """Fit method for model instances""" + + @abc.abstractmethod + def predict(self, *args: Any, **kwargs: Any) -> Any: + """Predict method for model instances""" + + @abc.abstractmethod + def serialize(self) -> ByteString: + """Serialize method for model instances""" + + @classmethod + @abc.abstractmethod + def deserialize(cls, instance_bytes: ByteString) -> "Model": + """Deserialize method for model instances""" + + +class DataAccessPlugin(NamedTuple): + """Object containing data access plugin function and call metadata""" + + name: str + """plugin name""" + function: Callable + """plugin function""" + state_cb_arg_name: Optional[str] = None + """name of the keyword argument for the state change cb""" + + +class InstantiatePlugin(NamedTuple): + """Object containing instantiate plugin function and call metadata""" + + function: Callable + """plugin function""" + state_cb_arg_name: Optional[str] = None + """name of the keyword argument for the state change cb""" + + +class FitPlugin(NamedTuple): + """Object containing fitting plugin function and call metadata""" + + function: Callable + """plugin function""" + state_cb_arg_name: Optional[str] = None + """name of the keyword argument for the state change cb""" + instance_arg_name: Optional[str] = None + """name of the keyword argument for the instance argument. If None, pass as + the first positional argument""" + + +class PredictPlugin(NamedTuple): + """Object containing prediction plugin function and call metadata""" + + function: Callable + """plugin function""" + state_cb_arg_name: Optional[str] = None + """name of the keyword argument for the state change cb""" + instance_arg_name: Optional[str] = None + """name of the keyword argument for the instance argument. If None, pass as + the first positional argument""" + + +class SerializePlugin(NamedTuple): + """Object containing serialize plugin function and call metadata""" + + function: Callable + """plugin function""" + + +class DeserializePlugin(NamedTuple): + """Object containing serialize plugin function and call metadata""" + + function: Callable + """plugin function""" + + +def initialize(conf: Dict): + """Imports the plugin modules, registering the entry point functions + + Args: + conf: configuration that follows schema under id + ``aimm://plugins/schema.yaml#``""" + for name in conf["names"]: + importlib.import_module(name) + + +StateCallback = Callable[[Dict], None] +StateCallback.__doc__ = """ +Generic state callback function signature a plugin would receive""" diff --git a/aimm/plugins/decorators.py b/aimm/plugins/decorators.py new file mode 100644 index 0000000..280b308 --- /dev/null +++ b/aimm/plugins/decorators.py @@ -0,0 +1,310 @@ +from typing import Callable, Dict, List, Optional, Type + +from aimm.plugins import common + + +_declarations_initial: Dict[str, Dict] = { + "data_access": {}, + "instantiate": {}, + "fit": {}, + "predict": {}, + "serialize": {}, + "deserialize": {}, +} +_declarations = dict(_declarations_initial) + + +def data_access( + name: str, state_cb_arg_name: Optional[str] = None +) -> Callable: + """Decorator used to indicate that the wrapped function is a data access + function. The decorated function can take any number of positional and + keyword arguments and should return the accessed data. + + Args: + name: name of the data access type + state_cb_arg_name: if set, indicates that the caller should pass a + state callback function as a keyword argument and use the passed + value as the argument name. The function is of type Callable[Any], + where the only argument is JSON serializable data. + + Returns: + Decorated function""" + + def decorator(function): + _declare( + "data_access", + name, + common.DataAccessPlugin( + function=function, + state_cb_arg_name=state_cb_arg_name, + name=name, + ), + ) + return function + + return decorator + + +def instantiate( + model_type: str, state_cb_arg_name: Optional[str] = None +) -> Callable: + """Decorator used to indicate that the wrapped function is a model instance + creation function. The decorated function should take any number of + positional and keyword arguments and should return the newly created model + instance. + + Args: + model_type: name of the model type + state_cb_arg_name: if set, indicates that the caller should pass a + state callback function as a keyword argument and use the passed + value as the argument name. The function is of type Callable[Any]. + + Returns: + Decorated function""" + + def decorator(function): + _declare( + "instantiate", + model_type, + common.InstantiatePlugin( + function=function, state_cb_arg_name=state_cb_arg_name + ), + ) + return function + + return decorator + + +def fit( + model_types: List[str], + state_cb_arg_name: Optional[str] = None, + instance_arg_name: Optional[str] = None, +) -> Callable: + """Decorator used to indicate that the wrapped function is a fitting + function. The decorated function should take at least one argument - model + instance (passed as the first positional argument by default). It may also + take any number of additional positional and keyword arguments and should + return the updated model instance. + + Args: + model_types: types of models supported by the decorated function + state_cb_arg_name: if set, indicates that the caller should pass a + state callback function as a keyword argument and use the passed + value as the argument name. The function is of type Callable[Any] + instance_arg_name: if set, indicates under which + argument name to pass the concrete model instance. If not set, it + is passed in the first positional argument + + Returns: + Decorated function""" + + def decorator(function): + for model_type in model_types: + if model_type in _declarations["fit"]: + raise ValueError( + f"duplicate fitting function under model " + f"type {model_type}" + ) + _declare( + "fit", + model_type, + common.FitPlugin( + function=function, + state_cb_arg_name=state_cb_arg_name, + instance_arg_name=instance_arg_name, + ), + ) + return function + + return decorator + + +def predict( + model_types: List[str], + state_cb_arg_name: Optional[str] = None, + instance_arg_name: Optional[str] = None, +) -> Callable: + """Decorator used to indicate that the wrapped function is a prediction + function. The decorated function should take at least one argument - model + instance (passed as the first positional argument by default). It may also + take any number of additional positional and keyword arguments and should + return the updated model instance. + + Args: + model_types: types of models supported by the decorated function + state_cb_arg_name: if set, indicates that the caller should pass a + state callback function as a keyword argument and use the passed + value as the argument name. The function is of type Callable[Any] + instance_arg_name: if set, indicates under which argument name to pass + the concrete model instance. If not set, it is passed in the first + positional argument + + Returns: + Decorated function""" + + def decorator(function): + for model_type in model_types: + _declare( + "predict", + model_type, + common.PredictPlugin( + function=function, + state_cb_arg_name=state_cb_arg_name, + instance_arg_name=instance_arg_name, + ), + ) + return function + + return decorator + + +def serialize(model_types: List[str]) -> Callable: + """Decorator used to indicate that the wrapped function is a serialize + function. The decorated function should have the following signature: + + ``(instance: Any) -> ByteString`` + + The return value is the byte representation of the model instance. + + Args: + model_types: types of models supported by the decorated function + + Returns: + Decorated function""" + + def decorator(function): + for model_type in model_types: + _declare( + "serialize", + model_type, + common.SerializePlugin(function=function), + ) + return function + + return decorator + + +def deserialize(model_types: List[str]) -> Callable: + """Decorator used to indicate that the wrapped function is a + deserialize function. The decorated function should have the following + signature: + + ``(instance_bytes: ByteString) -> Any`` + + The return value is the deserialized model instance. + + Args: + model_types: types of models supported by the decorated function + + Returns: + Decorated function""" + + def decorator(function): + for model_type in model_types: + _declare( + "deserialize", + model_type, + common.DeserializePlugin(function=function), + ) + return function + + return decorator + + +def model(cls: Type) -> Type: + """Model class decorator, used to mark that a class may be used as a model + implementation. Model class unifies different plugin actions + (:func:`instantiate` as ``__init__``, :func:`fit`, :func:`predict`, + :func:`serialize`, :func:`deserialize` as their same-named class methods) + under the same model type (module + class name). The class should implement + the :class:`Model` interface. + + Args: + cls (:class:`Model`): model class + + Returns: + Decorated class + """ + + model_type = f"{cls.__module__}.{cls.__name__}" + + _declare("instantiate", model_type, common.InstantiatePlugin(cls)) + + fit_fn = getattr(cls, "fit") + if isinstance(fit_fn, Callable): + _declare("fit", model_type, common.FitPlugin(fit_fn)) + + predict_fn = getattr(cls, "predict") + if isinstance(predict_fn, Callable): + _declare("predict", model_type, common.PredictPlugin(predict_fn)) + + serialize_fn = getattr(cls, "serialize") + if isinstance(serialize_fn, Callable): + _declare("serialize", model_type, common.SerializePlugin(serialize_fn)) + + deserialize_fn = getattr(cls, "deserialize") + if isinstance(deserialize_fn, Callable): + _declare( + "deserialize", model_type, common.DeserializePlugin(deserialize_fn) + ) + + return cls + + +def get_instantiate(model_type: str) -> common.InstantiatePlugin: + if model_type not in _declarations["instantiate"]: + raise ValueError( + f"no instantiation plugin for model type {model_type}" + ) + return _declarations["instantiate"][model_type] + + +def get_data_access(name: str) -> common.DataAccessPlugin: + if name not in _declarations["data_access"]: + raise ValueError(f"no data access plugin for name {name}") + return _declarations["data_access"][name] + + +def get_fit(model_type: str) -> common.FitPlugin: + if model_type not in _declarations["fit"]: + raise ValueError(f"no fit plugin for model type {model_type}") + return _declarations["fit"][model_type] + + +def get_predict(model_type: str) -> common.PredictPlugin: + if model_type not in _declarations["predict"]: + raise ValueError(f"no predict plugin for model type {model_type}") + return _declarations["predict"][model_type] + + +def get_serialize(model_type: str) -> common.SerializePlugin: + if model_type not in _declarations["serialize"]: + raise ValueError(f"no serialize plugin for model type {model_type}") + return _declarations["serialize"][model_type] + + +def get_deserialize(model_type: str) -> common.DeserializePlugin: + if model_type not in _declarations["deserialize"]: + raise ValueError(f"no deserialize plugin for model type {model_type}") + return _declarations["deserialize"][model_type] + + +def unload_all(): + global _declarations + _declarations = dict(_declarations_initial) + + +def _declare(declaration_type, key, plugin): + global _declarations + _declarations = dict(_declarations) + declarations_typed = dict(_declarations[declaration_type]) + + if key in declarations_typed: + raise ValueError( + f"plugin with type {key} already declared as " + f"{declaration_type}" + ) + declarations_typed[key] = plugin + + _declarations[declaration_type] = declarations_typed diff --git a/aimm/plugins/execute.py b/aimm/plugins/execute.py new file mode 100644 index 0000000..34a4452 --- /dev/null +++ b/aimm/plugins/execute.py @@ -0,0 +1,91 @@ +from typing import Any, ByteString + +from aimm.plugins import common +from aimm.plugins import decorators + + +def exec_data_access( + name: str, + state_cb: common.StateCallback = lambda state: None, + *args: Any, + **kwargs: Any +) -> Any: + """Uses a loaded plugin to access data""" + plugin = decorators.get_data_access(name) + kwargs = _kwargs_add_state_cb(plugin.state_cb_arg_name, state_cb, kwargs) + return plugin.function(*args, **kwargs) + + +def exec_instantiate( + model_type: str, + state_cb: common.StateCallback = lambda state: None, + *args: Any, + **kwargs: Any +) -> Any: + """Uses a loaded plugin to create a model instance""" + plugin = decorators.get_instantiate(model_type) + kwargs = _kwargs_add_state_cb(plugin.state_cb_arg_name, state_cb, kwargs) + return plugin.function(*args, **kwargs) + + +def exec_fit( + model_type: str, + instance: Any, + state_cb: common.StateCallback = lambda state: None, + *args: Any, + **kwargs: Any +) -> Any: + """Uses a loaded plugin to fit a model instance""" + plugin = decorators.get_fit(model_type) + kwargs = _kwargs_add_state_cb(plugin.state_cb_arg_name, state_cb, kwargs) + args, kwargs = _args_add_instance( + plugin.instance_arg_name, instance, args, kwargs + ) + return plugin.function(*args, **kwargs) + + +def exec_predict( + model_type: str, + instance: Any, + state_cb: common.StateCallback = lambda state: None, + *args: Any, + **kwargs: Any +) -> tuple[Any, Any]: + """Uses a loaded plugin to perform a prediction with a given model + instance. Also returns the instance because it might be altered during the + prediction, e.g. with reinforcement learning models.""" + plugin = decorators.get_predict(model_type) + kwargs = _kwargs_add_state_cb(plugin.state_cb_arg_name, state_cb, kwargs) + args, kwargs = _args_add_instance( + plugin.instance_arg_name, instance, args, kwargs + ) + return instance, plugin.function(*args, **kwargs) + + +def exec_serialize(model_type: str, instance: Any) -> ByteString: + """Uses a loaded plugin to convert model into bytes""" + plugin = decorators.get_serialize(model_type) + return plugin.function(instance) + + +def exec_deserialize(model_type: str, instance_bytes: ByteString) -> Any: + """Uses a loaded plugin to convert bytes into a model instance""" + plugin = decorators.get_deserialize(model_type) + return plugin.function(instance_bytes) + + +def _kwargs_add_state_cb(state_cb_arg_name, cb, kwargs): + if state_cb_arg_name: + if state_cb_arg_name in kwargs: + raise Exception("state cb already set") + kwargs = dict(kwargs, **{state_cb_arg_name: cb}) + return kwargs + + +def _args_add_instance(instance_arg_name, instance, args, kwargs): + if instance_arg_name: + if instance_arg_name in kwargs: + raise Exception("instance already set") + kwargs = dict(kwargs, **{instance_arg_name: instance}) + return args, kwargs + return (instance, *args), kwargs diff --git a/aimm/server/__init__.py b/aimm/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aimm/server/__main__.py b/aimm/server/__main__.py new file mode 100644 index 0000000..f73f758 --- /dev/null +++ b/aimm/server/__main__.py @@ -0,0 +1,7 @@ +import sys + +from aimm.server.main import main + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/aimm/server/__pycache__/__init__.cpython-312.pyc b/aimm/server/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..91cceb3 Binary files /dev/null and b/aimm/server/__pycache__/__init__.cpython-312.pyc differ diff --git a/aimm/server/__pycache__/common.cpython-312.pyc b/aimm/server/__pycache__/common.cpython-312.pyc new file mode 100644 index 0000000..3bc3534 Binary files /dev/null and b/aimm/server/__pycache__/common.cpython-312.pyc differ diff --git a/aimm/server/__pycache__/engine.cpython-312.pyc b/aimm/server/__pycache__/engine.cpython-312.pyc new file mode 100644 index 0000000..cc0e5ad Binary files /dev/null and b/aimm/server/__pycache__/engine.cpython-312.pyc differ diff --git a/aimm/server/__pycache__/main.cpython-312.pyc b/aimm/server/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..9e45449 Binary files /dev/null and b/aimm/server/__pycache__/main.cpython-312.pyc differ diff --git a/aimm/server/__pycache__/mprocess.cpython-312.pyc b/aimm/server/__pycache__/mprocess.cpython-312.pyc new file mode 100644 index 0000000..d4dee1b Binary files /dev/null and b/aimm/server/__pycache__/mprocess.cpython-312.pyc differ diff --git a/aimm/server/__pycache__/runners.cpython-312.pyc b/aimm/server/__pycache__/runners.cpython-312.pyc new file mode 100644 index 0000000..7615349 Binary files /dev/null and b/aimm/server/__pycache__/runners.cpython-312.pyc differ diff --git a/aimm/server/backend/__init__.py b/aimm/server/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aimm/server/backend/dummy.py b/aimm/server/backend/dummy.py new file mode 100644 index 0000000..06060ef --- /dev/null +++ b/aimm/server/backend/dummy.py @@ -0,0 +1,32 @@ +from hat import aio +import itertools + +from aimm.server import common + + +def create(_, __): + return DummyBackend() + + +class DummyBackend(common.Backend): + def __init__(self): + self._group = aio.Group() + self._id_counter = itertools.count(1) + + @property + def async_group(self) -> aio.Group: + """Async group""" + return self._group + + async def get_models(self): + return [] + + async def create_model(self, model_type, instance): + return common.Model( + model_type=model_type, + instance=instance, + instance_id=next(self._id_counter), + ) + + async def update_model(self, model): + return diff --git a/aimm/server/backend/event.py b/aimm/server/backend/event.py new file mode 100644 index 0000000..eb7d06f --- /dev/null +++ b/aimm/server/backend/event.py @@ -0,0 +1,98 @@ +from hat import aio +from hat import util +import base64 +import hat.event.common +import itertools + +from aimm.server import common +from aimm import plugins +from aimm.server.common import Model + + +def create_subscription(conf): + return [tuple([*conf["model_prefix"], "*"])] + + +async def create(conf, event_client): + common.json_schema_repo.validate("aimm://server/backend/event.yaml#", conf) + backend = EventBackend(conf, event_client) + await backend.start() + + return backend + + +class EventBackend(common.Backend): + def __init__(self, conf, event_client): + self._model_prefix = conf["model_prefix"] + self._executor = aio.create_executor() + self._cbs = util.CallbackRegistry() + self._group = aio.Group() + self._client = event_client + self._id_counter = None + + @property + def async_group(self) -> aio.Group: + """Async group""" + return self._group + + async def start(self): + models = await self.get_models() + self._id_counter = itertools.count( + max((model.instance_id for model in models), default=1) + ) + + async def get_models(self) -> list[Model]: + query_result = await self._client.query( + hat.event.common.QueryLatestParams( + event_types=[(*self._model_prefix, "*")] + ) + ) + return [await self._event_to_model(e) for e in query_result.events] + + async def create_model(self, model_type, instance): + model = common.Model( + model_type=model_type, + instance=instance, + instance_id=next(self._id_counter), + ) + await self._register_model(model) + return model + + async def update_model(self, model): + await self._register_model(model) + + def register_model_change_cb(self, cb): + self._cbs.register(cb) + + async def process_events(self, events): + for event in events: + self._cbs.notify(await self._event_to_model(event)) + + async def _register_model(self, model): + await self._client.register([await self._model_to_event(model)]) + + async def _model_to_event(self, model): + instance_b64 = base64.b64encode( + await self._executor( + plugins.exec_serialize, model.model_type, model.instance + ) + ).decode("utf-8") + return hat.event.common.RegisterEvent( + type=(*self._model_prefix, str(model.instance_id)), + source_timestamp=None, + payload=hat.event.common.EventPayloadJson( + {"type": model.model_type, "instance": instance_b64}, + ), + ) + + async def _event_to_model(self, event): + instance = await self._executor( + plugins.exec_deserialize, + event.payload.data["type"], + base64.b64decode(event.payload.data["instance"].encode("utf-8")), + ) + return common.Model( + instance=instance, + instance_id=int(event.type[len(self._model_prefix)]), + model_type=event.payload.data["type"], + ) diff --git a/aimm/server/backend/sqlite.py b/aimm/server/backend/sqlite.py new file mode 100644 index 0000000..347ea08 --- /dev/null +++ b/aimm/server/backend/sqlite.py @@ -0,0 +1,124 @@ +from functools import partial +from hat import aio +from pathlib import Path +import sqlite3 + +from aimm.server import common +from aimm import plugins + + +async def create(conf, _): + common.json_schema_repo.validate( + "aimm://server/backend/sqlite.yaml#", conf + ) + backend = SQLiteBackend(conf) + await backend.start() + return backend + + +class SQLiteBackend(common.Backend): + def __init__(self, conf): + self._conf = conf + self._executor = aio.create_executor(1) + self._connection = None + self._group = aio.Group() + + @property + def async_group(self) -> aio.Group: + """Async group""" + return self._group + + async def start(self): + self._connection = await self._executor( + _ext_db_connect, Path(self._conf["path"]) + ) + self._connection.row_factory = sqlite3.Row + self._group.spawn( + aio.call_on_cancel, self._executor, _ext_db_close, self._connection + ) + + async def get_models(self): + query = """SELECT * FROM models""" + cursor = await self._execute(query) + rows = await self._executor(_ext_fetchall, cursor) + models = [await self._row_to_model(row) for row in rows] + return models + + async def create_model(self, model_type, instance): + instance_blob = await self._executor( + plugins.exec_serialize, model_type, instance + ) + query = """INSERT INTO models + (type, instance) VALUES + (:model_type, :instance)""" + cursor = await self._execute( + query, model_type=model_type, instance=instance_blob + ) + return common.Model( + model_type=model_type, + instance=instance, + instance_id=cursor.lastrowid, + ) + + async def update_model(self, model): + instance_blob = await self._executor( + plugins.exec_serialize, model.model_type, model.instance + ) + query = """UPDATE models + SET instance=:instance + WHERE id=:instance_id""" + await self._execute( + query, instance=instance_blob, instance_id=model.instance_id + ) + + async def _execute(self, query, **kwargs): + return await self._executor( + partial(_ext_db_execute, self._connection, query, **kwargs) + ) + + async def _row_to_model(self, row): + model_type = row["type"] + return common.Model( + instance=await self._executor( + plugins.exec_deserialize, model_type, row["instance"] + ), + model_type=model_type, + instance_id=row["id"], + ) + + +def _ext_db_connect(path): + path.parent.mkdir(exist_ok=True) + conn = sqlite3.connect( + "file:{}?nolock=1".format(str(path)), + uri=True, + isolation_level=None, + detect_types=sqlite3.PARSE_DECLTYPES, + ) + _ext_db_execute( + conn, + """ + CREATE TABLE IF NOT EXISTS models ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + instance BLOB + );""", + ) + return conn + + +def _ext_fetchall(cursor): + return cursor.fetchall() + + +def _ext_db_execute(conn, query, cursor=None, **kwargs): + if not cursor: + cursor = conn.execute(query, kwargs) + else: + cursor.execute(query, kwargs) + conn.commit() + return cursor + + +def _ext_db_close(conn): + conn.close() diff --git a/aimm/server/common.py b/aimm/server/common.py new file mode 100644 index 0000000..59bef3b --- /dev/null +++ b/aimm/server/common.py @@ -0,0 +1,232 @@ +from hat import aio +from hat import util +from typing import ( + Any, + Dict, + Callable, + Iterable, + List, + NamedTuple, + Optional, + Collection, +) +import abc +import hat.event.eventer.client +import hat.event.common +import logging + +import aimm.common + +mlog = logging.getLogger(__name__) + +json_schema_repo = aimm.common.json_schema_repo + + +CreateSubscription = Callable[[Dict], hat.event.common.Subscription] +CreateSubscription.__doc__ = """ +Type of the ``create_subscription`` function that the dynamically imported +controls and backends may implement. Receives component configuration as the +only argument and returns a subscription object. +""" + + +class Model(NamedTuple): + """Server's representation of objects returned by + :func:`plugins.exec_instantiate`. Contains all metadata necessary to + identify and perform other actions with it.""" + + instance: Any + """instance""" + model_type: str + """model type, used to identify which plugin to use""" + instance_id: int + """instance id""" + + +class DataAccess(NamedTuple): + """Representation of a :func:`plugins.exec_data_access` call. May be passed + as an argument in some methods, indicating that data needs to be retrieved + prior to calling the main action. See more details on the exact method + docstrings.""" + + name: str + """name of the data access type, used to identify which plugin to use""" + args: Iterable + """positional arguments to be passed to the plugin call""" + kwargs: Dict[str, Any] + """keyword arguments to be passed to the plugin call""" + + +class Engine(aio.Resource, abc.ABC): + """Engine interface""" + + @property + @abc.abstractmethod + def state(self) -> Dict: + """Engine state, contains references to all models and actions. It's + never modified in-place, instead :meth:`subscribe_to_state_change` + should be used""" + + @abc.abstractmethod + def subscribe_to_state_change( + self, cb: Callable[[], None] + ) -> util.RegisterCallbackHandle: + """Subscribes to any changes to the engine state""" + + @abc.abstractmethod + def create_instance( + self, model_type: str, *args: Any, **kwargs: Any + ) -> "Action": + """Starts an action that creates a model instance and stores it in + state. + + Args: + model_type: model type + *args: instantiation arguments + **kwargs: instantiation keyword arguments""" + + @abc.abstractmethod + async def add_instance(self, model_type: str, instance: Any) -> Model: + """Adds existing instance to the state""" + + @abc.abstractmethod + async def update_instance(self, model: Model): + """Update existing instance in the state""" + + @abc.abstractmethod + def fit(self, instance_id: int, *args: Any, **kwargs: Any) -> "Action": + """Starts an action that fits an existing model instance. The used + fitting function is the one assigned to the model type. The instance, + while it is being fitted, is not accessible by any of the other + functions that would use it (other calls to fit, predictions, etc.). + + Args: + instance_id: id of model instance that will be fitted + *args: arguments to pass to the fitting function - if of type + :class:`aimm.server.common.DataAccess`, the value passed to the + fitting function is the result of the call to that plugin, + other arguments are passed directly + **kwargs: keyword arguments, work the same as the positional + arguments""" + + @abc.abstractmethod + def predict(self, instance_id: int, *args: Any, **kwargs: Any) -> "Action": + """Starts an action that uses an existing model instance to perform a + prediction. The used prediction function is the one assigned to model's + type. The instance, while prediction is called, is not accessible by + any of the other functions that would use it (other calls to predict, + fittings, etc.). If instance has changed while predicting, it is + updated in the state and database. + + Args: + instance_id: id of the model instance used for prediction + *args: arguments to pass to the predict function - if of type + :class:`aimm.server.common.DataAccess`, the value passed to the + predict function is the result of the call to that plugin, + other arguments are passed directly + **kwargs: keyword arguments, work the same as the positional + arguments + + Returns: + Reference to task of the manageable predict call, result of it is + the model's prediction""" + + +class Action(aio.Resource, abc.ABC): + """Represents a manageable call. Is an :class:`aio.Resource` so call can be + cancelled using ``async_close``.""" + + @abc.abstractmethod + async def wait_result(self) -> Any: + """Wait until call returns a result. May raise + :class:`asyncio.CancelledError` in case the call was cancelled.""" + + +def create_subscription(conf: Any) -> list[hat.event.common.EventType]: + """Placeholder of the backends and controls optional create subscription + function, needs to satisfy the given signature""" + + +def create_backend( + conf: Dict, event_client: Optional[hat.event.eventer.client.Client] = None +) -> "Backend": + """Placeholder of the backend's create function, needs to satisfy the given + signature""" + + +class Backend(aio.Resource, abc.ABC): + """Backend interface. In order to integrate in the aimm server, create a + module with the implementation and function ``create`` that creates a + backend instance. The function should have a signature as the + :func:`create_backend` function. + + The ``event_client`` argument is not ``None`` if backend module also + contains function named ``create_subscription`` with the same signature as + the :func:`create_subscription`. The function receives the same backend + configuration the ``create`` function would receive and returns the + subscription object for the backend. + """ + + @abc.abstractmethod + async def get_models(self) -> List[Model]: + """Get all persisted models, requires that a deserialization function + is defined for all persisted types + + Returns: + persisted models""" + + @abc.abstractmethod + async def create_model(self, model_type: str, instance: Any): + """Store a new model, requires that a serialization for the model type + is defined""" + + @abc.abstractmethod + async def update_model(self, model: Model): + """Replaces the old stored model with the new one, requires that a + serialization is defined for the model type""" + + def register_model_change_cb( + self, cb: Callable[[Model], None] + ) -> util.RegisterCallbackHandle: + """Register callback for backend-side model changes. Implementation + optional, defaults to ignoring the callback.""" + return util.RegisterCallbackHandle(cancel=lambda: None) + + async def process_events(self, events: hat.event.common.Event): + """Implementation optional. Called when event client receives events + matched by subscription from `create_backend_subscription`. Ignores + events by default, with a warning log.""" + mlog.warning( + "received events when no process_event method was implemented" + ) + + +def create_control( + conf: Dict, + engine: Engine, + event_client: Optional[hat.event.eventer.client.Client] = None, +) -> "Control": + """Placeholder of the control's create function, needs to satisfy the given + signature""" + + +class Control(aio.Resource, abc.ABC): + """Control interface. In order to integrate in the aimm server, create a + module with the implementation and function ``create`` that creates a + control instance and should have a signature as the :func:`create_control` + function. + + The ``event_client`` argument is not ``None`` if control module also + contains function named ``create_subscription`` with the same signature as + the :func:`create_subscription`. The function receives the same control + configuration the ``create`` function would receive and returns the list of + subscriptions ``ProxyClient`` should subscribe to. + """ + + async def process_events(self, events: Collection[hat.event.common.Event]): + """Implementation optional. Called when event client receives events + matched by subscription from `create_backend_subscription`. Ignores + events by default, with a warning log.""" + mlog.warning( + "received events when no process_event method was implemented" + ) diff --git a/aimm/server/control/__init__.py b/aimm/server/control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aimm/server/control/event.py b/aimm/server/control/event.py new file mode 100644 index 0000000..6a52220 --- /dev/null +++ b/aimm/server/control/event.py @@ -0,0 +1,248 @@ +from hat import aio +import asyncio +import base64 +import hat.event.common +import logging + +from aimm.server import common +from aimm import plugins + + +mlog = logging.getLogger(__name__) + + +def create_subscription(conf): + return [tuple([*p, "*"]) for p in conf["event_prefixes"].values()] + + +async def create(conf, engine, event_client): + common.json_schema_repo.validate("aimm://server/control/event.yaml#", conf) + if event_client is None: + raise ValueError( + "attempting to create event control without hat compatibility" + ) + return EventControl(conf, engine, event_client) + + +class EventControl(common.Control): + def __init__(self, conf, engine, event_client): + self._client = event_client + self._engine = engine + self._async_group = aio.Group() + self._event_prefixes = conf["event_prefixes"] + self._state_event_type = conf["state_event_type"] + self._action_state_event_type = conf["action_state_event_type"] + self._executor = aio.create_executor() + self._notified_state = {} + self._in_progress = {} + + self._notify_state() + self._engine.subscribe_to_state_change(self._notify_state) + + @property + def async_group(self) -> aio.Group: + """Async group""" + return self._async_group + + def _notify_state(self): + state_json = _state_to_json(self._engine) + if state_json == self._notified_state: + return + self.async_group.spawn( + self._client.register, + [_register_event(self._state_event_type, state_json)], + ) + self._notified_state = state_json + + async def process_events(self, events): + for event in events: + self.async_group.spawn(self._process_action, event) + + async def _process_action(self, event): + if self._prefix_match("create_instance", event): + await self._create_instance(event) + if self._prefix_match("add_instance", event): + await self._add_instance(event) + if self._prefix_match("update_instance", event): + await self._update_instance(event) + if self._prefix_match("fit", event): + await self._fit(event) + if self._prefix_match("predict", event): + await self._predict(event) + if self._prefix_match("cancel", event): + self._cancel(event) + + def _prefix_match(self, action_prefix, event): + if action_prefix not in self._event_prefixes: + return False + return hat.event.common.matches_query_type( + event.type, self._event_prefixes[action_prefix] + ["*"] + ) + + async def _create_instance(self, event): + try: + data = event.payload.data + model_type = data["model_type"] + args = [await self._process_arg(arg) for arg in data["args"]] + kwargs = { + k: await self._process_arg(v) + for k, v in data["kwargs"].items() + } + action = self._engine.create_instance(model_type, *args, **kwargs) + await self._register_action_state(event, "IN_PROGRESS") + self._in_progress[data["request_id"]] = action + try: + model = await action.wait_result() + await self._register_action_state( + event, "DONE", model.instance_id + ) + except asyncio.CancelledError: + await self._register_action_state(event, "CANCELLED") + finally: + del self._in_progress[data["request_id"]] + except Exception as e: + mlog.warning( + "instance creation failed with exception %s", e, exc_info=e + ) + await self._register_action_state(event, "FAILED") + + async def _add_instance(self, event): + try: + data = event.payload.data + instance = await self._instance_from_json( + data["instance"], data["model_type"] + ) + model = await self._engine.add_instance( + data["model_type"], instance + ) + await self._register_action_state(event, "DONE", model.instance_id) + except Exception as e: + mlog.warning( + "add instance failed with exception %s", e, exc_info=e + ) + await self._register_action_state(event, "FAILED") + + async def _update_instance(self, event): + try: + event_prefix = self._event_prefixes.get("update_instance") + instance_id = int(event.type[len(event_prefix)]) + data = event.payload.data + model_type = data["model_type"] + model = common.Model( + model_type=data["model_type"], + instance_id=instance_id, + instance=await self._instance_from_json( + data["instance"], model_type + ), + ) + await self._engine.update_instance(model) + await self._register_action_state(event, "DONE") + except Exception as e: + mlog.warning( + "update instance failed with exception %s", e, exc_info=e + ) + await self._register_action_state(event, "FAILED") + + async def _fit(self, event): + try: + event_prefix = self._event_prefixes["fit"] + data = event.payload.data + instance_id = int(event.type[len(event_prefix)]) + if instance_id not in self._engine.state["models"]: + raise ValueError("instance {instance_id} not in state") + args = [await self._process_arg(a) for a in data["args"]] + kwargs = { + k: await self._process_arg(v) + for k, v in data["kwargs"].items() + } + + action = self._engine.fit(instance_id, *args, **kwargs) + await self._register_action_state(event, "IN_PROGRESS") + self._in_progress[data["request_id"]] = action + try: + await action.wait_result() + await self._register_action_state(event, "DONE") + except asyncio.CancelledError: + await self._register_action_state(event, "CANCELLED") + finally: + del self._in_progress[data["request_id"]] + except Exception as e: + mlog.warning("fitting failed with exception %s", e, exc_info=e) + await self._register_action_state(event, "FAILED") + + async def _predict(self, event): + try: + event_prefix = self._event_prefixes["predict"] + data = event.payload.data + instance_id = int(event.type[len(event_prefix)]) + if instance_id not in self._engine.state["models"]: + raise ValueError("instance {instance_id} not in state") + args = [await self._process_arg(a) for a in data["args"]] + kwargs = { + k: await self._process_arg(v) + for k, v in data["kwargs"].items() + } + + action = self._engine.predict(instance_id, *args, **kwargs) + await self._register_action_state(event, "IN_PROGRESS") + self._in_progress[data["request_id"]] = action + try: + prediction = await action.wait_result() + await self._register_action_state(event, "DONE", prediction) + except asyncio.CancelledError: + await self._register_action_state(event, "CANCELLED") + finally: + del self._in_progress[data["request_id"]] + except Exception as e: + mlog.warning("prediction failed with exception %s", e, exc_info=e) + + def _cancel(self, event): + request_event_id = event.payload.data + if request_event_id in self._in_progress: + self._in_progress[request_event_id].close() + + async def _register_action_state(self, request_event, status, result=None): + return await self._client.register( + [ + _register_event( + self._action_state_event_type, + { + "request_id": request_event.payload.data["request_id"], + "status": status, + "result": result, + }, + ) + ] + ) + + async def _process_arg(self, arg): + if not (isinstance(arg, dict) and arg.get("type") == "data_access"): + return arg + return common.DataAccess( + name=arg["name"], args=arg["args"], kwargs=arg["kwargs"] + ) + + async def _instance_from_json(self, instance_b64, model_type): + return await self._executor( + plugins.exec_deserialize, + model_type, + base64.b64decode(instance_b64), + ) + + +def _state_to_json(engine): + return { + "models": { + instance_id: model.model_type + for instance_id, model in engine.state["models"].items() + }, + "actions": engine.state["actions"], + } + + +def _register_event(event_type, payload, source_timestamp=None): + return hat.event.common.RegisterEvent( + type=tuple(event_type), + source_timestamp=source_timestamp, + payload=hat.event.common.EventPayloadJson(payload), + ) diff --git a/aimm/server/control/repl.py b/aimm/server/control/repl.py new file mode 100644 index 0000000..2e9ef8a --- /dev/null +++ b/aimm/server/control/repl.py @@ -0,0 +1,253 @@ +from hat import aio +from hat import juggler +import base64 +import logging +import numpy +import pandas +import traceback + +from aimm.server import common +from aimm import plugins + + +mlog = logging.getLogger(__name__) + + +async def create(conf, engine, _): + common.json_schema_repo.validate("aimm://server/control/repl.yaml#", conf) + control = REPLControl(conf, engine) + return control + + +class REPLControl(common.Control): + def __init__(self, conf, engine): + self._conf = conf + self._engine = engine + self._group = aio.Group() + self._connection_session_mapping = {} + + self._group.spawn(self._run, conf["server"]) + + @property + def async_group(self) -> aio.Group: + """Async group""" + return self._group + + async def _run(self, conf): + server = await juggler.listen( + conf["host"], + conf["port"], + connection_cb=self._connection_cb, + request_cb=self._request_cb, + index_path=None, + ws_path="/", + pem_file=conf.get("pem_file"), + autoflush_delay=conf.get("autoflush_delay", 0.2), + shutdown_timeout=conf.get("shutdown_timeout", 0.1), + ) + _bind_resource(self._group, server) + await server.wait_closing() + + def _connection_cb(self, connection): + subgroup = self._group.create_subgroup() + session = Session(connection, self._engine, self._conf, subgroup) + self._connection_session_mapping[connection] = session + + async def _request_cb(self, connection, name, data): + session = self._connection_session_mapping[connection] + return await session.handle(name, data) + + +class Session(aio.Resource): + def __init__(self, connection, engine, conf, async_group): + self._connection = connection + self._engine = engine + self._conf = conf + self._user = None + self._async_group = async_group + + _bind_resource(self._async_group, self._connection) + + self._async_group.spawn(self._run) + + @property + def async_group(self): + return self._async_group + + async def handle(self, name, data): + if name == "login": + return self._login(data["username"], data["password"]) + elif name == "logout": + return self._logout() + elif name == "create_instance": + return await self._create_instance( + data["model_type"], data["args"], data["kwargs"] + ) + elif name == "add_instance": + return await self._add_instance( + data["model_type"], data["instance_b64"] + ) + elif name == "update_instance": + return await self._update_instance( + data["model_type"], data["instance_id"], data["instance_b64"] + ) + elif name == "fit": + return await self._fit( + data["instance_id"], data["args"], data["kwargs"] + ) + elif name == "predict": + return await self._predict( + data["instance_id"], data["args"], data["kwargs"] + ) + else: + return {"success": False} + + async def _run(self): + await self._on_state_change() + with self._engine.subscribe_to_state_change( + lambda: self.async_group.spawn(self._on_state_change) + ): + await self._connection.wait_closed() + + async def _on_state_change(self): + if self._user: + self._connection.state.set( + [], + await _generate_state( + self._engine.state["models"], self._engine.state["actions"] + ), + ) + else: + self._connection.state.set([], await _generate_state({}, {})) + + def _login(self, username, password): + if {"username": username, "password": password} in self._conf["users"]: + self._user = username + return {"success": True} + else: + self.close() + return {"success": False} + + def _logout(self): + self._user = None + self._connection.set_local_data(None) + + async def _create_instance(self, model_type, args, kwargs): + self._check_authorization() + args = [_arg_from_json(a) for a in args] + kwargs = {k: _arg_from_json(v) for k, v in kwargs.items()} + + action = self._engine.create_instance(model_type, *args, **kwargs) + model = await action.wait_result() + return await _model_to_json(model) + + async def _add_instance(self, model_type, instance): + self._check_authorization() + instance = await _model_from_json(instance, model_type) + model = await self._engine.add_instance(model_type, instance) + return await _model_to_json(model) + + async def _update_instance(self, model_type, instance_id, instance): + self._check_authorization() + model = common.Model( + model_type=model_type, + instance_id=instance_id, + instance=await _model_from_json(instance, model_type), + ) + await self._engine.update_instance(model) + return await _model_to_json(model) + + async def _fit(self, instance_id, args, kwargs): + self._check_authorization() + args = [_arg_from_json(a) for a in args] + kwargs = {k: _arg_from_json(v) for k, v in kwargs.items()} + + action = self._engine.fit(instance_id, *args, **kwargs) + model = await action.wait_result() + return await _model_to_json(model) + + async def _predict(self, instance_id, args, kwargs): + self._check_authorization() + args = [_arg_from_json(a) for a in args] + kwargs = {k: _arg_from_json(v) for k, v in kwargs.items()} + + action = self._engine.predict(instance_id, *args, **kwargs) + prediction = await action.wait_result() + return _prediction_to_json(prediction) + + def _check_authorization(self): + if self._user is None: + raise Exception("unauthorized action") + + +async def _generate_state(models, actions): + return { + "models": { + model_id: await _model_to_json(model) + for model_id, model in models.items() + }, + "actions": actions, + } + + +async def _model_to_json(model): + executor = aio.create_executor() + instance_bytes = base64.b64encode( + await executor( + plugins.exec_serialize, model.model_type, model.instance + ) + ).decode("utf-8") + return { + "instance_id": model.instance_id, + "model_type": model.model_type, + "instance": instance_bytes, + } + + +def _prediction_to_json(prediction): + if isinstance(prediction, pandas.DataFrame): + return {"type": "pandas_dataframe", "data": prediction.to_dict()} + if isinstance(prediction, pandas.Series): + return {"type": "pandas_series", "data": prediction.tolist()} + if isinstance(prediction, numpy.ndarray): + return {"type": "numpy_array", "data": prediction.tolist()} + return prediction + + +async def _model_from_json(instance_b64, model_type): + executor = aio.create_executor() + return await executor( + plugins.exec_deserialize, model_type, base64.b64decode(instance_b64) + ) + + +def _arg_from_json(arg): + if not isinstance(arg, dict): + return arg + if arg.get("type") == "data_access": + return common.DataAccess( + name=arg["name"], args=arg["args"], kwargs=arg["kwargs"] + ) + if arg.get("type") == "numpy_array": + return numpy.array(arg["data"], dtype=arg["dtype"]) + if arg.get("type") == "pandas_dataframe": + return pandas.DataFrame.from_dict(arg["data"]) + if arg.get("type") == "pandas_series": + return pandas.Series(arg["data"]) + return arg + + +def _exc_msg(e): + return { + "type": "result", + "success": False, + "exception": str(e), + "traceback": traceback.format_exc(), + } + + +def _bind_resource(async_group, resource): + async_group.spawn(aio.call_on_cancel, resource.async_close) + async_group.spawn( + aio.call_on_done, resource.wait_closing(), async_group.close + ) diff --git a/aimm/server/engine.py b/aimm/server/engine.py new file mode 100644 index 0000000..fdb0ecd --- /dev/null +++ b/aimm/server/engine.py @@ -0,0 +1,344 @@ +from functools import partial +from hat import aio +from hat import util +import asyncio +import itertools +import logging +import typing + +from aimm import plugins +from aimm.server import common +from aimm.server import mprocess + + +mlog = logging.getLogger(__name__) + + +async def create(conf: typing.Dict, backend: common.Backend) -> common.Engine: + """Create engine + + Args: + conf: configuration that follows schema with id + ``aimm://server/schema.yaml#/definitions/engine`` + backend: backend + + Returns: + engine + """ + engine = _Engine(conf, backend) + await engine.start() + return engine + + +class _Engine(common.Engine): + """Engine implementation, use :func:`create` to instantiate""" + + def __init__(self, conf, backend): + self._group = aio.Group() + self._backend = backend + self._conf = conf + self._state = {"actions": {}, "models": {}} + self._locks = { + instance_id: asyncio.Lock() + for instance_id in self._state["models"] + } + + self._action_id_gen = itertools.count(1) + + self._pool = mprocess.ProcessManager( + conf["max_children"], + self._group.create_subgroup(), + conf["check_children_period"], + conf["sigterm_timeout"], + ) + self._callback_registry = util.CallbackRegistry() + + @property + def async_group(self): + return self._group + + @property + def state(self): + return self._state + + async def start(self): + models = await self._backend.get_models() + self._state = { + "actions": {}, + "models": {model.instance_id: model for model in models}, + } + + def subscribe_to_state_change(self, cb): + return self._callback_registry.register(cb) + + def create_instance(self, model_type, *args, **kwargs): + action_id = next(self._action_id_gen) + state_cb = partial(self._update_action, action_id) + return create_action( + self._group.create_subgroup(), + self._act_create_instance, + model_type, + args, + kwargs, + state_cb, + ) + + async def add_instance(self, model_type, instance): + model = await self._backend.create_model(model_type, instance) + self._set_model(model) + return model + + async def update_instance(self, model: common.Model): + """Update existing instance in the state""" + self._set_model(model) + await self._backend.update_model(model) + + def fit(self, instance_id, *args, **kwargs): + action_id = next(self._action_id_gen) + state_cb = partial(self._update_action, action_id) + return create_action( + self._group.create_subgroup(), + self._act_fit, + instance_id, + args, + kwargs, + state_cb, + ) + + def predict(self, instance_id, *args, **kwargs): + action_id = next(self._action_id_gen) + state_cb = partial(self._update_action, action_id) + return create_action( + self._group.create_subgroup(), + self._act_predict, + instance_id, + args, + kwargs, + state_cb, + ) + + def _update_action(self, action_id, action_state): + actions = dict(self.state["actions"]) + actions.update({action_id: action_state}) + self._update_state(dict(self.state, actions=actions)) + + def _set_model(self, model): + if model.instance_id not in self._locks: + self._locks[model.instance_id] = asyncio.Lock() + models = dict(self.state["models"]) + models.update({model.instance_id: model}) + self._update_state(dict(self.state, models=models)) + + def _update_state(self, new_state): + self._state = new_state + self._callback_registry.notify() + + async def _act_create_instance(self, model_type, args, kwargs, state_cb): + reactive = _ReactiveState( + { + "meta": { + "call": "create_instance", + "model_type": model_type, + "args": [str(a) for a in args], + "kwargs": {k: str(v) for k, v in kwargs.items()}, + } + } + ) + reactive.register_state_change_cb(lambda: state_cb(reactive.state)) + + reactive.update(dict(reactive.state, progress="accessing_data")) + args, kwargs = await _derive_data_access_args( + self._pool, args, kwargs, reactive.register_substate("data_access") + ) + + reactive.update(dict(reactive.state, progress="executing")) + handler = self._pool.create_handler( + reactive.register_substate("action").update + ) + instance = await handler.run( + plugins.exec_instantiate, + model_type, + handler.proc_notify_state_change, + *args, + **kwargs + ) + + reactive.update(dict(reactive.state, progress="storing")) + model = await self._backend.create_model(model_type, instance) + self._set_model(model) + + reactive.update(dict(reactive.state, progress="complete")) + + return model + + async def _act_fit(self, instance_id, args, kwargs, state_cb): + reactive = _ReactiveState( + { + "meta": { + "call": "fit", + "model": instance_id, + "args": [str(a) for a in args], + "kwargs": {k: str(v) for k, v in kwargs.items()}, + } + } + ) + reactive.register_state_change_cb(lambda: state_cb(reactive.state)) + + reactive.update(dict(reactive.state, progress="accessing_data")) + args, kwargs = await _derive_data_access_args( + self._pool, args, kwargs, reactive.register_substate("data_access") + ) + + reactive.update(dict(reactive.state, progress="executing")) + handler = self._pool.create_handler( + reactive.register_substate("action").update + ) + + model = self.state["models"][instance_id] + async with self._locks[instance_id]: + instance = await handler.run( + plugins.exec_fit, + model.model_type, + model.instance, + handler.proc_notify_state_change, + *args, + **kwargs + ) + new_model = await self._update_model(instance, model, reactive) + reactive.update(dict(reactive.state, progress="complete")) + return new_model + + async def _act_predict(self, instance_id, args, kwargs, state_cb): + reactive = _ReactiveState( + { + "meta": { + "call": "predict", + "model": instance_id, + "args": [str(a) for a in args], + "kwargs": {k: str(v) for k, v in kwargs.items()}, + } + } + ) + reactive.register_state_change_cb(lambda: state_cb(reactive.state)) + + reactive.update(dict(reactive.state, progress="accessing_data")) + args, kwargs = await _derive_data_access_args( + self._pool, args, kwargs, reactive.register_substate("data_access") + ) + + handler = self._pool.create_handler( + reactive.register_substate("action").update + ) + async with self._locks[instance_id]: + model = self.state["models"][instance_id] + reactive.update(dict(reactive.state, progress="executing")) + instance, prediction = await handler.run( + plugins.exec_predict, + model.model_type, + model.instance, + handler.proc_notify_state_change, + *args, + **kwargs + ) + await self._update_model(instance, model, reactive) + reactive.update(dict(reactive.state, progress="complete")) + return prediction + + async def _update_model(self, instance, model, reactive): + new_model = common.Model( + instance=instance, + model_type=model.model_type, + instance_id=model.instance_id, + ) + reactive.update(dict(reactive.state, progress="storing")) + await self._backend.update_model(new_model) + + self._set_model(new_model) + return new_model + + +def create_action( + async_group: aio.Group, fn: typing.Callable, *args, **kwargs +) -> common.Action: + return _Action(async_group, fn, *args, **kwargs) + + +class _Action(common.Action): + def __init__(self, async_group, fn, *args, **kwargs): + self._group = async_group + self._task = self._group.spawn(fn, *args, **kwargs) + + @property + def async_group(self): + return self._group + + async def wait_result(self): + return await self._task + + +async def _derive_data_access_args(pool, args, kwargs, reactive_state): + actions = {} + async with aio.Group() as group: + for i, arg in enumerate(args): + if not isinstance(arg, common.DataAccess): + continue + actions[i] = group.spawn( + _get_data_access_action, pool, reactive_state, i, arg + ) + for key, value in kwargs.items(): + if not isinstance(value, common.DataAccess): + continue + actions[key] = group.spawn( + _get_data_access_action, pool, reactive_state, i, arg + ) + + if actions: + await asyncio.wait([task for task in actions.values()]) + args = list(args) + for key, task in actions.items(): + if isinstance(key, int): + args[key] = task.result() + elif isinstance(key, str): + kwargs[key] = task.result() + return args, kwargs + + +async def _get_data_access_action(pool, reactive_state, key, data_access): + handler = pool.create_handler(reactive_state.register_substate(key).update) + return await handler.run( + plugins.exec_data_access, + data_access.name, + handler.proc_notify_state_change, + *data_access.args, + **data_access.kwargs + ) + + +class _ReactiveState: + def __init__(self, state): + self._state = state + self._substates = {} + self._cb_registry = util.CallbackRegistry() + + @property + def state(self): + return self._state + + def register_state_change_cb(self, cb): + return self._cb_registry.register(cb) + + def update(self, state): + self._state = state + self._cb_registry.notify() + + def register_substate(self, key): + reactive = _ReactiveState(self._state.get(key, {})) + reactive.register_state_change_cb( + partial(self._on_substate_change, key) + ) + self._substates[key] = reactive + return reactive + + def _on_substate_change(self, key): + self._state = {**self._state, **{key: self._substates[key].state}} + self._cb_registry.notify() diff --git a/aimm/server/main.py b/aimm/server/main.py new file mode 100644 index 0000000..922b008 --- /dev/null +++ b/aimm/server/main.py @@ -0,0 +1,57 @@ +from hat import aio +from hat import json +from pathlib import Path +import appdirs +import argparse +import asyncio +import contextlib +import logging.config +import sys + +from aimm import plugins +from aimm.server import common +import aimm.server.runners + +mlog = logging.getLogger("aimm.server.main") +default_conf_path = Path(appdirs.user_data_dir("aimm")) / "server.yaml" + + +def main(): + aio.init_asyncio() + + args = _create_parser().parse_args() + conf = json.decode_file(args.conf) + common.json_schema_repo.validate("aimm://server/main.yaml#", conf) + + logging.config.dictConfig(conf["log"]) + plugins.initialize(conf["plugins"]) + with contextlib.suppress(asyncio.CancelledError): + aio.run_asyncio(async_main(conf)) + + +async def async_main(conf): + runner = aimm.server.runners.MainRunner(conf) + try: + await runner.wait_closing() + finally: + await aio.uncancellable(runner.async_close()) + + +def _create_parser(): + parser = argparse.ArgumentParser( + prog="aimm-server", description="Run AIMM server" + ) + parser.add_argument( + "--conf", + metavar="path", + dest="conf", + default=default_conf_path, + type=Path, + help="configuration defined by aimm://server/main.yaml# " + "(default $XDG_CONFIG_HOME/aimm/server.yaml)", + ) + return parser + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/aimm/server/mprocess.py b/aimm/server/mprocess.py new file mode 100644 index 0000000..a2b040f --- /dev/null +++ b/aimm/server/mprocess.py @@ -0,0 +1,243 @@ +"""Provides an interface to managed process calls. The main component is a +:class:`ProcessManager` object, that is used to create :class:`ProcessHandler` +objects, wrappers for the process calls.""" + +from hat import aio +from typing import Any, Callable, NamedTuple, Optional +import asyncio +import contextlib +import enum +import logging +import multiprocessing +import psutil +import signal + + +mlog = logging.getLogger(__name__) + +StateCallback = Callable[[Any], None] + +# TODO: switch to spawn +mp_context = multiprocessing.get_context("fork") + + +class ProcessManager(aio.Resource): + """Class used to create :class:`ProcessHandler` objects and limit the + amount of concurrently active child processes. + + Args: + max_children: maximum number of child processes that may be created + async_group: async group + check_children_period: number of seconds waited before checking if a + child process may be created and notifying pending handlers + sigterm_timeout: number of seconds waited before sending SIGKILL if a + child process does not terminate after SIGTERM""" + + def __init__( + self, + max_children: int, + async_group: aio.Group, + check_children_period: float, + sigterm_timeout: float, + ): + self._max_children = max_children + self._async_group = async_group + self._check_children_period = check_children_period + self._sigterm_timeout = sigterm_timeout + + self._condition = asyncio.Condition() + + self._async_group.spawn(self._condition_loop) + + @property + def async_group(self) -> aio.Group: + return self._async_group + + def create_handler(self, state_cb: StateCallback) -> "ProcessHandler": + """Creates a ProcessHandler + + Args: + state_cb (Callable[List[Any], None]): state callback for changes + in the process call + + Returns: + ProcessHandler""" + return ProcessHandler( + self._async_group.create_subgroup(), + self._sigterm_timeout, + state_cb, + self._condition, + ) + + async def _condition_loop(self): + condition = self._condition + process = psutil.Process() + with contextlib.suppress(asyncio.CancelledError): + while True: + async with condition: + available_processes = self._max_children - len( + process.children() + ) + if available_processes > 0: + condition.notify(available_processes) + await asyncio.sleep(self._check_children_period) + + +class ProcessHandler(aio.Resource): + """Handler for calls in separate processes. Created through + :meth:`ProcessManager.create`. + + Args: + async_group (hat.aio.Group): async group + sigterm_timeout (float): time waited until process handles SIGTERM + before sending SIGKILL during forced shutdown + state_cb (Optional[Callable[Any]]): state change cb + condition (asyncio.Condition): condition that notifies when a new + process may be created + """ + + def __init__( + self, + async_group: aio.Group, + sigterm_timeout: float, + state_cb: StateCallback, + condition: asyncio.Condition, + ): + self._async_group = async_group + self._sigterm_timeout = sigterm_timeout + self._state_cb = state_cb + self._condition = condition + + self._result_pipe = mp_context.Pipe(False) + self._state_pipe = mp_context.Pipe(False) + + self._process = None + self._executor = aio.create_executor() + + self._async_group.spawn(self._state_loop) + self._async_group.spawn(aio.call_on_cancel, self._cleanup) + + @property + def async_group(self) -> aio.Group: + return self._async_group + + def proc_notify_state_change(self, state: Any): + """To be passed to and ran in the separate process call. Notifies the + handler of state change, new state is passed to ``state_cb`` received + in the constructor. + + Args: + state: call state, needs to be pickleable + + """ + self._state_pipe[1].send(state) + + async def run(self, fn: Callable, *args: Any, **kwargs: Any): + """Requests the start of function execution in the separate process. + + Args: + fn: function that will be called + *args: positional arguments, need to be pickleable + **kwargs: keyword arguments, need to be pickleable + + """ + await self._condition.acquire() + try: + await self._condition.wait() + self._process = mp_context.Process( + target=_proc_run_fn, + args=(self._result_pipe, fn, *args), + kwargs=kwargs, + ) + self._process.start() + + async def wait_result(): + result = await self._executor( + _ext_closeable_recv, self._result_pipe + ) + if result.success: + return result.result + else: + raise result.exception + + return await aio.uncancellable(wait_result()) + finally: + self._condition.release() + self._async_group.close() + + async def _state_loop(self): + with contextlib.suppress(asyncio.CancelledError, ValueError): + while True: + state = await self._executor( + _ext_closeable_recv, self._state_pipe + ) + if self._state_cb: + self._state_cb(state) + + async def _cleanup(self): + if self._process is not None: + await self._executor( + _ext_end_process, self._process, self._sigterm_timeout + ) + await self._executor(_ext_close_pipe, self._result_pipe) + await self._executor(_ext_close_pipe, self._state_pipe) + + +@contextlib.contextmanager +def sigterm_override(): + try: + signal.signal(signal.SIGTERM, _plugin_sigterm_handler) + yield + finally: + signal.signal(signal.SIGTERM, signal.SIG_DFL) + + +class _Result(NamedTuple): + success: bool + result: Optional[Any] = None + exception: Optional[Exception] = None + + +class ProcessTerminatedException(Exception): + pass + + +def _plugin_sigterm_handler(_, __): + raise ProcessTerminatedException("process sigterm") + + +def _proc_run_fn(pipe, fn, *args, **kwargs): + try: + with sigterm_override(): + result = _Result(success=True, result=fn(*args, **kwargs)) + except Exception as e: + result = _Result(success=False, exception=e) + pipe[1].send(result) + + +def _ext_end_process(process, sigterm_timeout): + if process.is_alive(): + process.terminate() + process.join(sigterm_timeout) + if process.is_alive(): + process.kill() + process.join() + process.close() + + +class _PipeSentinel(enum.Enum): + CLOSE = enum.auto + + +def _ext_close_pipe(pipe): + _, send_conn = pipe + send_conn.send(_PipeSentinel.CLOSE) + send_conn.close() + + +def _ext_closeable_recv(pipe): + recv_conn, _ = pipe + value = recv_conn.recv() + if value == _PipeSentinel.CLOSE: + raise ProcessTerminatedException("pipe closed") + return value diff --git a/aimm/server/runners.py b/aimm/server/runners.py new file mode 100644 index 0000000..2a0fb30 --- /dev/null +++ b/aimm/server/runners.py @@ -0,0 +1,226 @@ +import importlib +import logging +from typing import Any + +from hat import aio +import hat.event.component +import hat.monitor.component +import hat.event.common +import hat.event.eventer.client +from hat.drivers import tcp +import aimm.server.engine + +mlog = logging.getLogger(__name__) + + +class MainRunner(aio.Resource): + def __init__(self, conf): + self._conf = conf + self._group = aio.Group() + + self._group.spawn(self._run) + + @property + def async_group(self) -> aio.Group: + return self._group + + async def _run(self): + if "hat" in self._conf: + child_runner = HatRunner(self._conf) + else: + mlog.debug("running without hat compatibility") + child_runner = AIMMRunner(self._conf, client=None) + _bind_resource(self._group, child_runner) + try: + await child_runner.wait_closing() + except Exception as e: + mlog.error("main runner loop error: %s", e, exc_info=e) + finally: + await aio.uncancellable(child_runner.async_close()) + + +class HatRunner(aio.Resource): + def __init__(self, conf): + self._conf = conf + self._group = aio.Group() + + self._aimm_runner = None + self._hat_component = None + self._eventer_client = None + + self._subscriptions = [] + self._backend_subscription = None + module = importlib.import_module(conf["backend"]["module"]) + if hasattr(module, "create_subscription"): + subscription = module.create_subscription(conf["backend"]) + self._subscriptions.extend(subscription) + self._backend_subscription = hat.event.common.create_subscription( + subscription + ) + + self._control_subscriptions = [] + for control_id, control_conf in enumerate(conf["control"]): + module = importlib.import_module(control_conf["module"]) + if hasattr(module, "create_subscription"): + subscription = module.create_subscription(control_conf) + self._subscriptions.extend(subscription) + self._control_subscriptions.append( + ( + hat.event.common.create_subscription(subscription), + control_id, + ) + ) + + self._group.spawn(self._run) + + @property + def async_group(self) -> aio.Group: + return self._group + + async def _run(self): + hat_conf = self._conf["hat"] + if monitor_conf := hat_conf.get("monitor_component"): + if event_server_group := monitor_conf.get("event_server_group"): + + async def events_cb(_, __, events): + await self._on_events(events) + + self._hat_component = await hat.event.component.connect( + addr=tcp.Address( + host=monitor_conf["host"], port=monitor_conf["port"] + ), + name=self._conf["name"], + group=monitor_conf["group"], + server_group=event_server_group, + client_name=f"aimm/{self._conf["name"]}", + runner_cb=( + lambda _, __, client: self._create_aimm_runner(client) + ), + events_cb=events_cb, + eventer_kwargs={"subscriptions": self._subscriptions}, + ) + else: + self._hat_component = await hat.monitor.component.connect( + addr=tcp.Address( + host=monitor_conf["host"], port=monitor_conf["port"] + ), + name=self._conf["name"], + group=monitor_conf["group"], + runner_cb=lambda _: self._create_aimm_runner(None), + ) + await self._hat_component.set_ready(True) + _bind_resource(self._group, self._hat_component) + elif eventer_conf := hat_conf.get("eventer_server"): + + async def on_eventer_events(_, events): + await self._on_events(events) + + self._eventer_client = await hat.event.eventer.connect( + addr=tcp.Address(eventer_conf["host"], eventer_conf["port"]), + client_name=self._conf["name"], + status_cb=None, + events_cb=on_eventer_events, + subscriptions=self._subscriptions, + ) + _bind_resource(self._group, self._eventer_client) + self._create_aimm_runner(self._eventer_client) + + try: + await self._group.wait_closing() + except Exception as e: + mlog.error("unhandled exception in hat runner: %s", e, exc_info=e) + finally: + await aio.uncancellable(self._cleanup()) + + async def _cleanup(self): + if self._aimm_runner: + await self._aimm_runner.async_close() + if self._hat_component: + await self._hat_component.async_close() + if self._eventer_client: + await self._eventer_client.async_close() + + def _create_aimm_runner(self, eventer_client): + self._aimm_runner = AIMMRunner(conf=self._conf, client=eventer_client) + _bind_resource(self._group, self._aimm_runner) + return self._aimm_runner + + async def _on_events(self, events): + if self._aimm_runner is None: + return + if self._backend_subscription: + backend_events = [ + e for e in events if self._backend_subscription.matches(e.type) + ] + await self._aimm_runner.notify_backend(backend_events) + for subscription, control_id in self._control_subscriptions: + control_events = [ + e for e in events if subscription.matches(e.type) + ] + await self._aimm_runner.notify_control(control_id, control_events) + + +class AIMMRunner(aio.Resource): + def __init__(self, conf, client): + self._conf = conf + self._group = aio.Group() + self._client = client + self._module_map = {} + + self._backend = None + self._engine = None + self._controls = [] + + self._group.spawn(self._run) + + @property + def async_group(self) -> aio.Group: + return self._group + + async def notify_backend(self, data: Any): + await self._backend.process_events(data) + + async def notify_control(self, control_id: int, data: Any): + await self._controls[control_id].process_events(data) + + async def _run(self): + async for resource in self._create_resources(): + _bind_resource(self._group, resource) + + try: + await self._group.wait_closing() + except Exception as e: + mlog.error("error in aimm runner: %s", e, exc_info=e) + + async def _create_resources(self): + self._backend = await self._create_backend(self._conf["backend"]) + yield self._backend + + self._engine = await aimm.server.engine.create( + self._conf["engine"], self._backend + ) + yield self._engine + + for control_conf in self._conf["control"]: + control = await self._create_control(control_conf, self._engine) + self._controls.append(control) + yield control + + async def _create_backend(self, backend_conf): + module = importlib.import_module(backend_conf["module"]) + backend = await aio.call(module.create, backend_conf, self._client) + self._module_map[self._conf["backend"]["module"]] = backend + return backend + + async def _create_control(self, control_conf, engine): + module = importlib.import_module(control_conf["module"]) + control = await aio.call( + module.create, control_conf, engine, self._client + ) + self._module_map[control_conf["module"]] = control + return control + + +def _bind_resource(group, resource): + group.spawn(aio.call_on_cancel, resource.async_close) + group.spawn(aio.call_on_done, resource.wait_closing(), group.close) diff --git a/build/docs/.buildinfo b/build/docs/.buildinfo new file mode 100644 index 0000000..3a5cc10 --- /dev/null +++ b/build/docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: f8f475548174c62d75185b8975743a17 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/build/docs/.doctrees/cite.doctree b/build/docs/.doctrees/cite.doctree new file mode 100644 index 0000000..a1a534f Binary files /dev/null and b/build/docs/.doctrees/cite.doctree differ diff --git a/build/docs/.doctrees/clients/index.doctree b/build/docs/.doctrees/clients/index.doctree new file mode 100644 index 0000000..7ed7793 Binary files /dev/null and b/build/docs/.doctrees/clients/index.doctree differ diff --git a/build/docs/.doctrees/clients/repl.doctree b/build/docs/.doctrees/clients/repl.doctree new file mode 100644 index 0000000..0f96b16 Binary files /dev/null and b/build/docs/.doctrees/clients/repl.doctree differ diff --git a/build/docs/.doctrees/environment.pickle b/build/docs/.doctrees/environment.pickle new file mode 100644 index 0000000..8d4e682 Binary files /dev/null and b/build/docs/.doctrees/environment.pickle differ diff --git a/build/docs/.doctrees/getting_started.doctree b/build/docs/.doctrees/getting_started.doctree new file mode 100644 index 0000000..cb9607c Binary files /dev/null and b/build/docs/.doctrees/getting_started.doctree differ diff --git a/build/docs/.doctrees/index.doctree b/build/docs/.doctrees/index.doctree new file mode 100644 index 0000000..d13606a Binary files /dev/null and b/build/docs/.doctrees/index.doctree differ diff --git a/build/docs/.doctrees/introduction.doctree b/build/docs/.doctrees/introduction.doctree new file mode 100644 index 0000000..e176751 Binary files /dev/null and b/build/docs/.doctrees/introduction.doctree differ diff --git a/build/docs/.doctrees/plugins.doctree b/build/docs/.doctrees/plugins.doctree new file mode 100644 index 0000000..745b2e1 Binary files /dev/null and b/build/docs/.doctrees/plugins.doctree differ diff --git a/build/docs/.doctrees/server/backend.doctree b/build/docs/.doctrees/server/backend.doctree new file mode 100644 index 0000000..bec10ab Binary files /dev/null and b/build/docs/.doctrees/server/backend.doctree differ diff --git a/build/docs/.doctrees/server/control/event.doctree b/build/docs/.doctrees/server/control/event.doctree new file mode 100644 index 0000000..ed93e04 Binary files /dev/null and b/build/docs/.doctrees/server/control/event.doctree differ diff --git a/build/docs/.doctrees/server/control/index.doctree b/build/docs/.doctrees/server/control/index.doctree new file mode 100644 index 0000000..d9e5352 Binary files /dev/null and b/build/docs/.doctrees/server/control/index.doctree differ diff --git a/build/docs/.doctrees/server/control/repl.doctree b/build/docs/.doctrees/server/control/repl.doctree new file mode 100644 index 0000000..63123d0 Binary files /dev/null and b/build/docs/.doctrees/server/control/repl.doctree differ diff --git a/build/docs/.doctrees/server/engine.doctree b/build/docs/.doctrees/server/engine.doctree new file mode 100644 index 0000000..e8f2d3c Binary files /dev/null and b/build/docs/.doctrees/server/engine.doctree differ diff --git a/build/docs/.doctrees/server/index.doctree b/build/docs/.doctrees/server/index.doctree new file mode 100644 index 0000000..201b094 Binary files /dev/null and b/build/docs/.doctrees/server/index.doctree differ diff --git a/build/docs/_images/server.svg b/build/docs/_images/server.svg new file mode 100644 index 0000000..4984ec7 --- /dev/null +++ b/build/docs/_images/server.svg @@ -0,0 +1,3 @@ + + +
Engine
Engine
Control
Control
Backend
Backend
Plugins
Plugins
Hat event interface
Hat event inte...
Hat event interface
Hat event inte...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/build/docs/_sources/cite.rst.txt b/build/docs/_sources/cite.rst.txt new file mode 100644 index 0000000..e4cfbcf --- /dev/null +++ b/build/docs/_sources/cite.rst.txt @@ -0,0 +1,19 @@ +Citing AIMM +=========== + +If you reference AIMM in your research please consider citing the paper that +specifies its architecture and interfaces. The paper is available `here +`_. You can also use the +following BiBTeX entry: + +.. code-block:: bibtex + + @ARTICLE{9734070, + author={Sičanica, Zlatan and Sučić, Stjepan and Milašinović, Boris}, + journal={IEEE Access}, + title={Architecture of an Artificial Intelligence Model Manager for Event-Driven Component-Based SCADA Systems}, + year={2022}, + volume={10}, + number={}, + pages={30414-30426}, + doi={10.1109/ACCESS.2022.3159715}} diff --git a/build/docs/_sources/clients/index.rst.txt b/build/docs/_sources/clients/index.rst.txt new file mode 100644 index 0000000..d4200b7 --- /dev/null +++ b/build/docs/_sources/clients/index.rst.txt @@ -0,0 +1,11 @@ +Clients +======= + +The aimm package provides applications and libraries that can be used to +communicate with some of the initially available :doc:`server controls +`. These are referred to as clients and the purpose of this +chapter provides their documentations. + +.. toctree:: + + repl diff --git a/build/docs/_sources/clients/repl.rst.txt b/build/docs/_sources/clients/repl.rst.txt new file mode 100644 index 0000000..19e0dda --- /dev/null +++ b/build/docs/_sources/clients/repl.rst.txt @@ -0,0 +1,12 @@ +REPL +==== + +REPL client is a Python library that communicates with the :doc:`REPL control +<../../server/control/repl>` and provides an interface that triggers +interactions with it. Its intended purpose is to be imported and used in REPL +environment, but there are no real restrictions on it being used as a library. +While client implementation is contained within the ``aimm.client.repl`` +module, which is specified hereafter. + +.. automodule:: aimm.client.repl + :members: diff --git a/build/docs/_sources/getting_started.rst.txt b/build/docs/_sources/getting_started.rst.txt new file mode 100644 index 0000000..d744323 --- /dev/null +++ b/build/docs/_sources/getting_started.rst.txt @@ -0,0 +1,52 @@ +Getting started +=============== + +After :doc:`installing AIMM `, its CLI and libraries become +available. Since the main purpose of the server is providing an API to +dynamically defined plugins, the first step is defining the plugins the server +will provide an interface for. The following code shows an example of a module +containing multiple plugin definitions: + +.. literalinclude:: ../examples/0001/plugins/sklearn_wrapper.py + +The plugins defined are: + + * data access for Iris dataset inputs and outputs + * model implementation that is actually a wrapper around sklearn's SVC + +For more information on the plugin interface, consult the :doc:`documentation +entry `. + +The next step is server configuration. This involves writing a YAML file +containing the necessary settings. An example of such a configuration, +following up to the previous example, may be: + +.. literalinclude:: ../examples/0001/aimm.yaml + :language: yaml + +For more details on configuring and running the server, consult the +:doc:`documentation entry `. + +Running the server now starts a service that listens on the address +``127.0.0.1:9999`` and allows clients to connect, create, fit and use SVC +models. Since the only configured control is REPL control, it should be +possible to connect using the :doc:`REPL client `. Following +snippet shows how the library may be used: + +.. code-block:: python + + from aimm.client import repl + + async def run(): + aimm = repl.AIMM() + await aimm.connect('ws://127.0.0.1:9999/ws') + m = await aimm.create_instance('plugins.sklearn.SVC') + + await m.fit(repl.DataAccessArg('iris_inputs'), repl.DataAccessArg('iris_outputs')) + await m.predict(repl.DataAccessArg('iris_inputs')) + +The code would connect to the server, create an SVC instance, remotely fit it +with Iris data and use it to perform classification. + +Example of Hat integration is available in the `examples directory +`_. diff --git a/build/docs/_sources/index.rst.txt b/build/docs/_sources/index.rst.txt new file mode 100644 index 0000000..fcbd126 --- /dev/null +++ b/build/docs/_sources/index.rst.txt @@ -0,0 +1,12 @@ +Content +------- + +.. toctree:: + :maxdepth: 1 + + introduction + getting_started + plugins + server/index + clients/index + cite diff --git a/build/docs/_sources/introduction.rst.txt b/build/docs/_sources/introduction.rst.txt new file mode 100644 index 0000000..f473ca8 --- /dev/null +++ b/build/docs/_sources/introduction.rst.txt @@ -0,0 +1,5 @@ +Introduction +============ + +.. include:: ../README.rst + :start-line: 2 diff --git a/build/docs/_sources/plugins.rst.txt b/build/docs/_sources/plugins.rst.txt new file mode 100644 index 0000000..189ef9a --- /dev/null +++ b/build/docs/_sources/plugins.rst.txt @@ -0,0 +1,64 @@ +Plugins +======= + +AIMM system relies on user-written plugins for implementations of machine +learning models, preprocessing and data-fetching methods. Both server and its +clients may use the plugin API. The plugins are implemented as Python modules +that are dynamically imported at some point during servers or clients runtime. +Within these modules, plugin implementators should use the decorators from the +``aimm.plugins`` module to indicate which functions and classes are entry +points to various machine learning workflow actions. They may also, through +decorator arguments, pass information to plugin callers, allowing them to +further declare semantics of some of their arguments (e.g. declare that an +argument should receive a callback function for progress notification). + +Before accessing any plugins, they need to be imported and initialized. One way +to do this (other then importing them manually) is by calling the +initialization function: + +.. autofunction:: aimm.plugins.initialize + +Initialize configuration schema: + +.. literalinclude:: ../schemas_json/plugins.yaml + :language: yaml + + +.. warning:: The AIMM plugin interface does not create a sandbox environment + when executing plugins, it is up to the implementator to make sure not to + perform unsafe actions + +Decorators +---------- + +.. autodecorator:: aimm.plugins.data_access +.. autodecorator:: aimm.plugins.instantiate +.. autodecorator:: aimm.plugins.fit +.. autodecorator:: aimm.plugins.predict +.. autodecorator:: aimm.plugins.serialize +.. autodecorator:: aimm.plugins.deserialize + +It is common for a model type to have all of the above defined functions, for +this reason the following class and decorator are introduced: + +.. autoclass:: aimm.plugins.Model + :members: +.. autofunction:: aimm.plugins.model + + +Calling plugins +--------------- + +After implementing and loading plugins, they can be called using following +functions: + +.. autofunction:: aimm.plugins.exec_data_access +.. autofunction:: aimm.plugins.exec_instantiate +.. autofunction:: aimm.plugins.exec_fit +.. autofunction:: aimm.plugins.exec_predict +.. autofunction:: aimm.plugins.exec_serialize +.. autofunction:: aimm.plugins.exec_deserialize + +State callbacks have the following signature: + +.. autodata:: aimm.plugins.StateCallback diff --git a/build/docs/_sources/server/backend.rst.txt b/build/docs/_sources/server/backend.rst.txt new file mode 100644 index 0000000..07b704f --- /dev/null +++ b/build/docs/_sources/server/backend.rst.txt @@ -0,0 +1,75 @@ +Backend +======= + +The function of backend instances is performing model persistance. They provide +functions that allow its callers to store and retrieve modules. + +The backend configuration is one of the properties in the server configuration. +Schema does not set limits on which backend implementations are available and +instead offers an interface that allows dynamic imports of backend +implementations, requiring only a ``module`` parameter that is a Python module +name of the concrete backend implementation. The exact minimal schema is as +follows: + +.. literalinclude:: ../../schemas_json/server/backend/main.yaml + :language: yaml + +All backend implementations need to implement the following interface: + +.. autoclass:: aimm.server.common.Backend + :members: +.. autofunction:: aimm.server.common.create_backend +.. autofunction:: aimm.server.common.create_subscription + +Only one instance of a backend can be configured on a aimm server and its +configuration depends on the concrete implementation of the interface. Two +kinds of backend implementations are available with the aimm package - sqlite +and event backend. + +SQLite +------ + +SQLite backend stores all the models into a SQLite database. The configuration +needs to follow the schema: + +.. literalinclude:: ../../schemas_json/server/backend/sqlite.yaml + :language: yaml + +Only configuration property is ``path`` - path to the SQLite file where data is +stored. + +Event +----- + +The event backend makes use of Hat's event infrastructure to create and access +events that contain model blobs. It requires a connection to hat event server +to be configured and its configuration needs to follow the schema: + +.. literalinclude:: ../../schemas_json/server/backend/event.yaml + :language: yaml + +The only configurable property, ``model_prefix`` is the prefix of the model +blob event's types. The events that the model raises will have the following +structure: + + * event type: ``[, ]`` + * source timestamp: None + * payload: dictionary that follows the schema: + + .. code-block:: yaml + + --- + type: object + required: + - type + - instance + properties: + type: + type: string + description: | + model type, used to pair with deserialization function, + when needed + instance: + type: string + description: base64 encoded serialized instance + ... diff --git a/build/docs/_sources/server/control/event.rst.txt b/build/docs/_sources/server/control/event.rst.txt new file mode 100644 index 0000000..3a12a24 --- /dev/null +++ b/build/docs/_sources/server/control/event.rst.txt @@ -0,0 +1,261 @@ +Event +===== + +Event control communicates with hat's event server and, responding to events, +calls engine's methods and reports their results over the same event bus. The +control's configuration needs to satisfy the following schema: + +.. literalinclude:: ../../../schemas_json/server/control/event.yaml + :language: yaml + +State +----- + +Upon any engine state change, event control registers an event with the +following structure: + + * type: ``[]`` + * source_timestamp is None + * payload: JSON with following schema: + + .. code-block:: yaml + + --- + type: object + required: + - models + - actions + properties: + models: + type: object + patternProperties: + '(.)+': + type: string + description: model type + actions: + type: object + description: copied as-is from engine's state + ... + +Action requests and states +-------------------------- + +Engine's functions are used as reactions to received events. Control receives +events with types that match one of the ``[, '*']`` query types and +calls the functions depending on which exact prefix has matched. + +Prior to the calling the functions, arguments passed in the payload may be +preprocessed and converted into :class:`aimm.server.common.DataAccess` objects +of they have a specific structure. Arguments of any call specified in the +request event's payload should have the following structure: + +.. code-block:: yaml + + --- + id: '#object_arg' + oneOf: + - {} + - description: converted to ``common.DataAccess`` + type: object + required: + - type + - name + - args + - kwargs + properties: + type: + const: data_access + name: + type: string + description: data access plugin name + args: + type: array + items: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +The control registers events that refer directly to the request that started +the action and that contain information on the actions state, and, if an action +returns it, its result. The event has the following structure: + + * type: ``[]`` + * source_timestamp is None + * payload: JSON with the following schema: + + .. code-block:: yaml + + --- + type: object + required: + - request_id + - result + properties: + request_id: + type: object + description: | + event id that started the execution + status: + enum: + - IN_PROGRESS + - DONE + - FAILED + - CANCELLED + result: {} + ... + +Create instance +''''''''''''''' + +Incoming event structure: + + * type: ``[, '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - model_type + - args + - kwargs + properties: + model_type: + type: string + args: + type: array + items: + type: '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +Result passed in the response is either an integer, representing ID of the +generated instance, or ``None`` if creation has failed. + + +Add instance +'''''''''''' + +Incoming event structure: + + * type: ``[, '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - model_type + - instance + properties: + model_type: + type: string + instance: + type: string + description: base64 encoded instance + ... + +Result passed in the response is either an integer, representing ID of the +generated instance, or ``None`` if creation has failed. + +Update instance +''''''''''''''' + +Incoming event structure: + + * type: ``[, , '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - model_type + - instance + properties: + model_type: + type: string + instance: + type: string + description: base64 encoded instance + ... + +Result is a boolean, ``true`` if update was performed successfully. + +Fit +''' + +Incoming event structure: + + * type: ``[, , '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - args + - kwargs + properties: + args: + type: array + items: + type: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +Result is a boolean, ``true`` if update was performed successfully. + +Predict +''''''' + +Incoming event structure: + + * type: ``[, , '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - args + - kwargs + properties: + args: + type: array + items: + type: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +Result is the prediction, exact value returned by the call to the predict +plugin. + +Cancel +'''''' + +Any action that can have the state ``IN_PROGRESS`` may be canceled. This is +done by registering a cancel event, with the following structure: + + * type ``[, '*']`` + * JSON payload that is a dictionary representation of the id of the event + that started the action diff --git a/build/docs/_sources/server/control/index.rst.txt b/build/docs/_sources/server/control/index.rst.txt new file mode 100644 index 0000000..4ef5dcc --- /dev/null +++ b/build/docs/_sources/server/control/index.rst.txt @@ -0,0 +1,30 @@ +Control +======= + +Control interface is used to communicate to external actors and call engine's +functions. They may notify their respective actors of state changes and serve +as a general entry point for any external client. + +Control configuration is one of the properties in the server configuration. +Similar to backend, control interface works on the principle of dynamic +imports, configuration schema only consisting of a list of objects (multiple +controls may run in parallel) that require a ``module`` parameter, which is a +Python module name that contains the concrete control implementation. The exact +configuration schema looks as follows: + +.. literalinclude:: ../../../schemas_json/server/control/main.yaml + :language: yaml + +All control implementations should implement the following interface: + +.. autoclass:: aimm.server.common.Control + :members: +.. autofunction:: aimm.server.common.create_control +.. autofunction:: aimm.server.common.create_subscription + :no-index: + +.. toctree:: + :maxdepth: 1 + + repl + event diff --git a/build/docs/_sources/server/control/repl.rst.txt b/build/docs/_sources/server/control/repl.rst.txt new file mode 100644 index 0000000..de4785d --- /dev/null +++ b/build/docs/_sources/server/control/repl.rst.txt @@ -0,0 +1,272 @@ +REPL +==== + +REPL control serves as an interface to the REPL client. Its configuration has +the following schema: + +.. literalinclude:: ../../../schemas_json/server/control/repl.yaml + :language: yaml + +REPL control works as a Websocket server, using the hat-juggler protocol. It +translates engine's state into JSON serializable data and transfers it to its +clients over local-remote data synchronization. It also provides its clients an +RPC interface, which allows them to send messages causing calls to engine's +functions and receive their results. + +The server listens at the address ``ws://:/ws``, where ``host`` and +``port`` are configuration parameters. After connecting, the clients have +access to the initial state and RPC actions. + +State +----- + +Before accessing the state, clients need to be authorized (described in the +Actions section). The initial state, before login is just an empty JSON object. +After successful login, engine's state is translated into JSON with the +following schema: + +.. code-block:: yaml + + --- + type: object + required: + - actions + - models + properties: + actions: + type: object + patternProperties: + "(.)+": + type: object + description: copied directly from engine's state property + models: + type: object + patternProperties: + "(.)+": + type: object + required: + - type + - id + - instance + properties: + type: + type: string + id: + type: integer + instance: + type: string + description: base64 encoded instance + ... + +RPC interface +------------- + +RPC interface provides several actions its clients may call. These actions are +documented in this section, along with some additional considerations about +result JSON representations and advanced argument passing. + + +Actions +""""""" + +``login`` +''''''''' + +Authorizes the user with given username and password. Successful authorization +gives access to other actions and full state. + +Arguments: + * `username` (``str``) + * `password` (``str``) + +.. note:: Login procedure here is added pro forma and might not provide optimal + level of security for some use cases. If this is the case, it is advised to + develop a separate control that implements a more appropriate procedure. + +``logout`` +'''''''''' + +Logs the user out. + +``create_instance`` +''''''''''''''''''' + +Connects to engine's ``create_instance`` method. Argument preprocessing is +supported. + +Arguments: + * `model_type` (``str``): model type as defined in plugins + * `args` (``List[Any]``): positional arguments passed to the plugin method + * `kwargs` (``Dict[str: Any]``): keyword arguments passed to the plugin + method + +Returns JSON representation of the model. + +``add_instance`` +'''''''''''''''' + +Connects to engine's ``add_instance`` method. + +Arguments: + * `model_type` (``str``): model type as defined in plugins + * `instance` (``str``): base64 encoded serialized model instance + +Returns JSON representation of the model. + +``update_instance`` +''''''''''''''''''' + +Connects to engine's ``update_instance`` method. + +Arguments: + * `model_type` (``str``): model type as defined in plugins + * `instance_id` (``int``): ID of the instance that is being updated + * `instance` (``str``): base64 encoded serialized model instance + +Returns JSON representation of the model. + +``fit`` +''''''' + +Connects to engine's ``fit`` method. Argument preprocessing is supported. + +Arguments: + * `instance_id` (``int``): ID of the instance that is being fitted + * `args` (``List[Any]``): positional arguments for the fitting method + * `kwargs` (``Dict[str, Any]``): keyword arguments for the fitting method + +Returns JSON representation of the model. + +``predict`` +''''''''''' + +Connects to engine's ``predict`` method. Argument preprocessing is supported. + +Arguments: + * `instance_id` (``int``): ID of the instance that is being fitted + * `args` (``List[Any]``): positional arguments for the fitting method + * `kwargs` (``Dict[str, Any]``): keyword arguments for the fitting method + +Returns prediction converted to JSON. + +JSON representations +"""""""""""""""""""" + +Some data structures mentioned in the sections above are, by the default, not +JSON serializable and JSON schema of the structure they take is described in +this section. + +Models schema: + +.. code-block:: yaml + + --- + type: object + required: + - instance_id + - model_type + - instance + properties: + instance_id: + type: integer + model_type: + type: string + instance: + type: string + description: base64 encoded serialized model instance + ... + + +Prediction schema: + + +.. code-block:: yaml + + --- + oneOf: + - {} + - type: object + required: + - type + - data + properties: + type: + enum: + - numpy_array + - pandas_dataframe + - pandas_series + data: + type: object + description: | + prediction result serialized as json, for numpy + arrays and pandas Series, tolist methods are used + and for dataframe, its to_dict method is used + ... + +Argument preprocessing +"""""""""""""""""""""" + +Actions correspond to engine's interface, and that interface provides support +for passing :class:`aimm.server.common.DataAccess` objects, to signify that an +engine action needs to execute a data access plugin before calling the main +action. Control allows sections of messages that contain arguments to take a +specific structure that signify that that argument needs to be converted into +an object. Conversion to :class:`aimm.server.common.DataAccess`, +:class:`numpy.array` and :class:`pandas.DataFrame` is currently supported. The +structure: + +.. code-block:: yaml + + --- + id: '#object_arg' + oneOf: + - {} + - description: converts to common.DataAccess + required: + - name + - args + - kwargs + properties: + type: + const: data_access + name: + type: string + description: data access plugin name + args: + type: array + items: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + - description: converts to numpy.array + required: + - data + - dtype + properties: + type: + const: numpy_array + data: + type: array + dtype: + type: string + - description: converts to pandas.DataFrame + required: + - data + properties: + type: + const: pandas_dataframe + data: + type: object + descirption: result of pandas.DataFrame.to_dict function + - description: converts to pandas.Series + required: + - data + properties: + type: + const: pandas_series + data: + type: object + descirption: result of pandas.Series.tolist function + ... diff --git a/build/docs/_sources/server/engine.rst.txt b/build/docs/_sources/server/engine.rst.txt new file mode 100644 index 0000000..fe0cb15 --- /dev/null +++ b/build/docs/_sources/server/engine.rst.txt @@ -0,0 +1,161 @@ +Engine +====== + +Engine is the central component of the AIMM system. Its purpose is handling +manageable calls to plugins, using the backend when persistence is required and +serving its state to all controllers. + +Engine's part of the configuration needs to satisfy the following schema: + +.. literalinclude:: ../../schemas_json/server/engine.yaml + :language: yaml + +When engine is started, it queries its backend to access all existing model +instances. The instances are then stored in engine's state. Any component +holding a reference to the engine may use it to perform different actions, such +as creating model instances, fitting them or using them for predictions. When a +new model instance is created, or an old one is updated, state change is +notified to any components that have subscribed to state changes. Additionally, +calls to the plugins themselves are manageable and engine keeps theirs states +in its state as well. + +Workflow actions such as fitting or using models are ran asynchronously in +separate asyncio tasks. Additionally, any plugin they call is ran in a separate +process, wrapped in a handler that allows subscriptions to call's state (if the +plugin call supports state notification). The calls can also be cancelled, +which is done using signals - initially SIGTERM and later SIGKILL if a +configured timeout expires. Also, to avoid fork bombs, a separate pseudo-pool +is implemented to serve as an interface for process creation, disallowing +creation of new processes if a certain number of subprocesses is already +running. + +Engine configuration reflects the multiprocessing nature of the implementation, +since all of the options refer to different timeouts and limitations: + + * ``sigterm_timeout`` is the number of seconds waited after SIGTERM was sent + to a process before SIGKILL is sent if it doesn't terminate + * ``max_children`` is the maximum amount of children - concurrent subprocesses + that can run at the same time + * ``check_children_period`` - the check for children counts is done + periodically and this setting indicates how often it is checked + +Engine module provides the following interface: + +.. autoclass:: aimm.server.common.Engine + :members: + +.. autoclass:: aimm.server.common.Action + :members: + +Since there are no strict limitations on the arguments that may be passed to +plugins, i.e., positional and keyword arguments are mostly passed as-is, +callers of the actions have the options of passing different special kinds of +objects as arguments. These objects are interpreted by the engine as subactions +that need to be executed before the main action. E.g., a fitting function may +expect a dataset as an input, and while it is possible to pass the dataset +directly to engine's :meth:`Engine.fit` call, the caller could create a +:class:`aimm.server.common.DataAccess` object and pass it instead. This would +indicate to the engine that it needs to use the data access plugin to access +the required data before fitting. All subactions are also ran in a separate +subprocesses and notify their progress through state. + +State +----- + +State is a dictionary consisting of two properties, ``models`` and ``actions``. +Models are a dictionary with instance IDs as keys and +:class:`aimm.server.common.Model` instances as values. Actions are also a +dictionary, with the following structure: + +.. code-block:: yaml + + --- + description: keys are action IDs + patternProperties: + '(.)+': + oneOf: + - type: 'null' + description: prior to start of the action call + - type: object + required: + - meta + - progress + properties: + meta: + type: object + required: + - call + properties: + call: + type: string + description: call that the action is making + model_type: + type: string + model: + type: integer + args: + type: array + kwargs: + type: object + progress: + enum: + - accessing_data + - executing + - complete + data_access: + type: object + description: | + keys represent argument IDs (numbers for + positional, strings for named), values are set by + plugin's state callbacks + action: + description: set by plugin state callback + ... + +Multiprocessing +--------------- + +The details of the multiprocessing implementation are placed in a separate +module, ``aimm.server.mprocess``. This module is in charge of providing an +interface for managed process calls. The central class of the module is the +:class:`aimm.server.mprocess.ProcessManager`. Its purpose is similar to one of +a standard :class:`multiprocessing.Pool`, main difference being that it does +not keep an exact amount of process workers alive at all times and instead +holds an :class:`asyncio.Condition` that prevents creation of new processes +until the number of children is under the ``max_children`` configuration +parameter. + +The manager is implemented in the following class: + +.. autoclass:: aimm.server.mprocess.ProcessManager + :members: + +The process calls are wrapped in a :class:`aimm.server.mprocess.ProcessHandler` +instance, whose interface allows callers to terminate the process call. It also +allows callers to pass their state change callback functions which are called +whenever the process' state changes. + +After calling :meth:`aimm.server.mprocess.ProcessManager.create_handler` and +receiving a process handler, the call can be made using the +:meth:`aimm.server.mprocess.ProcessHandler.run` function, which, in reality, +first spawns an asyncio task that blocks until the process manager allows +creation of a new process and only then actually creates a new process. + +The state notification is done using callbacks and multiprocessing pipes. +Process handler receives a ``state_cb`` argument in its constructor and this is +the function used to notify states to the rest of the system. It also provides +a method ``proc_notify_state_change``, which is a callback passed to the +function running in the separate process. This function uses a +:class:`multiprocessing.Pipe` object to send function's state values (need to +be pickle-able). Handlers also have internal state listening loops, running in +the main asyncio event loop, that react to receiving these state changes and +notify the rest of the system using the ``state_cb`` passed in the constructor. +Result of the separated process call is also passed through a separate pipe and +set as the result of the :data:`aimm.server.mprocess.ProcessHandler.result` +property. + +The complete class docstring: + +.. autoclass:: aimm.server.mprocess.ProcessHandler + :members: + :noindex: diff --git a/build/docs/_sources/server/index.rst.txt b/build/docs/_sources/server/index.rst.txt new file mode 100644 index 0000000..5a71c13 --- /dev/null +++ b/build/docs/_sources/server/index.rst.txt @@ -0,0 +1,106 @@ +Server +====== + +AIMM server is a server application that listens on one or several interfaces +(web services, multiprocess communication, etc.) and allows the clients that +connect to them to perform various operations with its configured plugins. + +Usage +----- + +After successful installation, the server can be run by calling +``aimm-server`` in the command line. The command has the following interface: + + .. program-output:: python -m aimm.server.main --help + + +Configuration +------------- + +To use the server, it needs to be configured. The configuration is a YAML file +that contains options specific to different components of which the system +consists. It also contains options for logging and Hat configuration. It needs +to validate against the following schema: + +.. literalinclude:: ../../schemas_json/server/main.yaml + :language: yaml + +Logging configuration is mandatory and its values are passed directly to the +:func:`logging.basicConfig` function. Remaining properties of the root part +of the configuration directly correlate to the main components of the server's +architecture, are more complex and will be explained separately. + +Hat +--- + +AIMM server configuration contains options that allow it to work as a part of +`Hat`_ infrastructure. The optional ``hat`` property of the configuration +contains the options that allow this integration. + +.. _hat: https://hat-open.com/ + +Connection to monitor is the minimal requirement in order to integrate to the +Hat network and the configuration parameters are as specified by the `monitor +documentation`_. Additionally, if ``event_server_group`` field is set, AIMM +server will also attempt to connect to event server with in the given group. +If these properties are configured but the server cannot connect to Hat +components, it will halt and wait until it manages to connect. + +.. _monitor documentation: https://hat-monitor.hat-open.com/ + +Since AIMM server supports dynamic imports of different backend and control +implementations that may use the event server connection to receive and +register events, the modules containing these components need to implement a +``get_subscriptions`` function that returns a list of event server +subscriptions. It should have the following type: + +.. autodata:: aimm.server.common.CreateSubscription + + +A combined list of all these subscriptions is then used as the global +subscription of the entire server. If the ``get_subscriptions`` function is +provided, the component implementation will receive an instance of a +:class:`hat.event.eventer.Client`, as specified `here`_. + +.. _here: https://hat-event.hat-open.com/py_api/hat/event/eventer.html#Client + +Architecture +------------ + +After initialization and, optionally, Hat integration, the architecture of AIMM +server may be represented with the following diagram: + +.. image:: server.svg + +The main components are: + + * engine - implements function calls that connect control interfaces with + plugins, keeps the state of the application and uses backend to store it + when neccessary + * backend - handles state data persistence + * plugins - contain model and data access implementations + * control - provides external interfaces for clients, calls engine functions + and notifies clients of state changes or call results + +Common data structures +---------------------- + +The server has some common data structures used in different components, +required as arguments at some methods or returned as their results. They are +documented in this section. + +.. autoclass:: aimm.server.common.Model + :members: +.. autoclass:: aimm.server.common.DataAccess + :members: + + +Components +---------- + +.. toctree:: + :maxdepth: 1 + + engine + backend + control/index diff --git a/build/docs/_static/_sphinx_javascript_frameworks_compat.js b/build/docs/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8141580 --- /dev/null +++ b/build/docs/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/build/docs/_static/basic.css b/build/docs/_static/basic.css new file mode 100644 index 0000000..f316efc --- /dev/null +++ b/build/docs/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/build/docs/_static/css/badge_only.css b/build/docs/_static/css/badge_only.css new file mode 100644 index 0000000..c718cee --- /dev/null +++ b/build/docs/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/build/docs/_static/css/fonts/Roboto-Slab-Bold.woff b/build/docs/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/build/docs/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/build/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 b/build/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/build/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/build/docs/_static/css/fonts/Roboto-Slab-Regular.woff b/build/docs/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/build/docs/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/build/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 b/build/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/build/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/build/docs/_static/css/fonts/fontawesome-webfont.eot b/build/docs/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/build/docs/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/build/docs/_static/css/fonts/fontawesome-webfont.svg b/build/docs/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/build/docs/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/docs/_static/css/fonts/fontawesome-webfont.ttf b/build/docs/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/build/docs/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/build/docs/_static/css/fonts/fontawesome-webfont.woff b/build/docs/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/build/docs/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/build/docs/_static/css/fonts/fontawesome-webfont.woff2 b/build/docs/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/build/docs/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/build/docs/_static/css/fonts/lato-bold-italic.woff b/build/docs/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/build/docs/_static/css/fonts/lato-bold-italic.woff differ diff --git a/build/docs/_static/css/fonts/lato-bold-italic.woff2 b/build/docs/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/build/docs/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/build/docs/_static/css/fonts/lato-bold.woff b/build/docs/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/build/docs/_static/css/fonts/lato-bold.woff differ diff --git a/build/docs/_static/css/fonts/lato-bold.woff2 b/build/docs/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/build/docs/_static/css/fonts/lato-bold.woff2 differ diff --git a/build/docs/_static/css/fonts/lato-normal-italic.woff b/build/docs/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/build/docs/_static/css/fonts/lato-normal-italic.woff differ diff --git a/build/docs/_static/css/fonts/lato-normal-italic.woff2 b/build/docs/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/build/docs/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/build/docs/_static/css/fonts/lato-normal.woff b/build/docs/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/build/docs/_static/css/fonts/lato-normal.woff differ diff --git a/build/docs/_static/css/fonts/lato-normal.woff2 b/build/docs/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/build/docs/_static/css/fonts/lato-normal.woff2 differ diff --git a/build/docs/_static/css/theme.css b/build/docs/_static/css/theme.css new file mode 100644 index 0000000..19a446a --- /dev/null +++ b/build/docs/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/build/docs/_static/doctools.js b/build/docs/_static/doctools.js new file mode 100644 index 0000000..4d67807 --- /dev/null +++ b/build/docs/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/build/docs/_static/documentation_options.js b/build/docs/_static/documentation_options.js new file mode 100644 index 0000000..7e4c114 --- /dev/null +++ b/build/docs/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/build/docs/_static/file.png b/build/docs/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/build/docs/_static/file.png differ diff --git a/build/docs/_static/jquery.js b/build/docs/_static/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/build/docs/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/build/docs/_static/js/html5shiv.min.js b/build/docs/_static/js/html5shiv.min.js new file mode 100644 index 0000000..cd1c674 --- /dev/null +++ b/build/docs/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/build/docs/_static/js/theme.js b/build/docs/_static/js/theme.js new file mode 100644 index 0000000..1fddb6e --- /dev/null +++ b/build/docs/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/build/docs/_static/minus.png b/build/docs/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/build/docs/_static/minus.png differ diff --git a/build/docs/_static/plus.png b/build/docs/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/build/docs/_static/plus.png differ diff --git a/build/docs/_static/pygments.css b/build/docs/_static/pygments.css new file mode 100644 index 0000000..84ab303 --- /dev/null +++ b/build/docs/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/build/docs/_static/searchtools.js b/build/docs/_static/searchtools.js new file mode 100644 index 0000000..b08d58c --- /dev/null +++ b/build/docs/_static/searchtools.js @@ -0,0 +1,620 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/build/docs/_static/sphinx_highlight.js b/build/docs/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/build/docs/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/build/docs/cite.html b/build/docs/cite.html new file mode 100644 index 0000000..0a0d3ab --- /dev/null +++ b/build/docs/cite.html @@ -0,0 +1,130 @@ + + + + + + + Citing AIMM — aimm documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Citing AIMM

+

If you reference AIMM in your research please consider citing the paper that +specifies its architecture and interfaces. The paper is available here. You can also use the +following BiBTeX entry:

+
@ARTICLE{9734070,
+  author={Sičanica, Zlatan and Sučić, Stjepan and Milašinović, Boris},
+  journal={IEEE Access},
+  title={Architecture of an Artificial Intelligence Model Manager for Event-Driven Component-Based SCADA Systems},
+  year={2022},
+  volume={10},
+  number={},
+  pages={30414-30426},
+  doi={10.1109/ACCESS.2022.3159715}}
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/clients/index.html b/build/docs/clients/index.html new file mode 100644 index 0000000..a36c085 --- /dev/null +++ b/build/docs/clients/index.html @@ -0,0 +1,155 @@ + + + + + + + Clients — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Clients

+

The aimm package provides applications and libraries that can be used to +communicate with some of the initially available server controls. These are referred to as clients and the purpose of this +chapter provides their documentations.

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/clients/repl.html b/build/docs/clients/repl.html new file mode 100644 index 0000000..1d53fc3 --- /dev/null +++ b/build/docs/clients/repl.html @@ -0,0 +1,269 @@ + + + + + + + REPL — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

REPL

+

REPL client is a Python library that communicates with the REPL control and provides an interface that triggers +interactions with it. Its intended purpose is to be imported and used in REPL +environment, but there are no real restrictions on it being used as a library. +While client implementation is contained within the aimm.client.repl +module, which is specified hereafter.

+

REPL client module. Provides a minimal interface that follows the protocol +specified by the REPL control.

+
+
+class aimm.client.repl.AIMM
+

Class that manages connections to AIMM REPL control, directly maps +available functions to its methods

+
+
+property async_group: Group
+

Async group

+
+ +
+
+property address: str | None
+

Current address object is connected to

+
+ +
+
+property state: None | bool | int | float | str | List[JSON] | Dict[str, JSON]
+

Current state reported from the AIMM server

+
+ +
+
+async connect(address: str)
+

Connects to the specified remote address. Login data is received +from a user prompt. Passwords are hashed with SHA-256 before sending +login request.

+
+ +
+
+async create_instance(model_type: str, *args: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]], **kwargs: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]]) Model
+

Creates a model instance on the remote server

+
+ +
+
+async add_instance(model_type: str, instance: Any) Model
+

Adds an existing instance on the remote server

+
+ +
+
+async update_instance(model_type: str, instance_id: int, instance: Any) Model
+

Replaces an existing instance with a new one

+
+ +
+
+async fit(instance_id: int, *args: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]], **kwargs: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]]) Model
+

Fits an instance on the remote server

+
+ +
+
+async predict(instance_id: int, *args: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]], **kwargs: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]]) Any
+

Uses an instance on the remote server for a prediction

+
+ +
+ +
+
+class aimm.client.repl.Model(aimm: AIMM, instance_id: int, model_type: str)
+

Represents an AIMM model instance and provides a simplified interface +for using or changing it remotely.

+
+
+async fit(*args: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]], **kwargs: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]])
+

Fits the model

+
+ +
+
+async predict(*args: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]], **kwargs: DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]]) Any
+

Uses the model to generate a prediction

+
+ +
+ +
+
+class aimm.client.repl.DataAccessArg(name: str, args: List[PluginArg] = [], kwargs: Dict[str, PluginArg] = {})
+

If passed as an argument, remote server calls a data access plugin and +passes its result instead of this object

+
+
+name: str
+

name of the remote data access plugin

+
+ +
+
+args: List[DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]]]
+

positional arguments for the data access plugin call

+
+ +
+
+kwargs: Dict[str, DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[None | bool | int | float | str | List[JSON] | Dict[str, JSON]] | Dict[str, None | bool | int | float | str | List[JSON] | Dict[str, JSON]]]
+

keyword arguments for the data access plugin call

+
+ +
+ +
+
+aimm.client.repl.PluginArg
+

Represents a generic, plugin-specific argument

+

alias of DataAccessArg | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes] | DataFrame | Series | None | List[JSON] | Dict[str, JSON]

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/genindex.html b/build/docs/genindex.html new file mode 100644 index 0000000..d202095 --- /dev/null +++ b/build/docs/genindex.html @@ -0,0 +1,428 @@ + + + + + + Index — aimm documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | I + | K + | M + | N + | P + | R + | S + | U + | W + +
+

A

+ + + +
+ +

B

+ + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + +
+ +

G

+ + +
+ +

I

+ + + +
+ +

K

+ + +
+ +

M

+ + + +
+ +

N

+ + +
+ +

P

+ + + +
+ +

R

+ + +
+ +

S

+ + + +
+ +

U

+ + + +
+ +

W

+ + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2024, Zlatan Sičanica.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/getting_started.html b/build/docs/getting_started.html new file mode 100644 index 0000000..522a13a --- /dev/null +++ b/build/docs/getting_started.html @@ -0,0 +1,229 @@ + + + + + + + Getting started — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Getting started

+

After installing AIMM, its CLI and libraries become +available. Since the main purpose of the server is providing an API to +dynamically defined plugins, the first step is defining the plugins the server +will provide an interface for. The following code shows an example of a module +containing multiple plugin definitions:

+
from aimm import plugins
+from sklearn import svm
+from sklearn import datasets
+import pickle
+
+
+@plugins.data_access("iris_inputs")
+def iris_inputs():
+    return datasets.load_iris(return_X_y=True)[0]
+
+
+@plugins.data_access("iris_outputs")
+def iris_outputs():
+    return datasets.load_iris(return_X_y=True)[1]
+
+
+@plugins.model
+class SVC(plugins.Model):
+    def __init__(self, gamma=0.001, C=100.0):
+        self._svc = svm.SVC(gamma=gamma, C=C)
+
+    def fit(self, X, y):
+        self._svc = self._svc.fit(X, y)
+        return self
+
+    def predict(self, X):
+        return self._svc.predict(X)
+
+    def serialize(self):
+        return pickle.dumps(self)
+
+    @classmethod
+    def deserialize(cls, instance_bytes):
+        return pickle.loads(instance_bytes)
+
+
+

The plugins defined are:

+
+
    +
  • data access for Iris dataset inputs and outputs

  • +
  • model implementation that is actually a wrapper around sklearn’s SVC

  • +
+
+

For more information on the plugin interface, consult the documentation +entry.

+

The next step is server configuration. This involves writing a YAML file +containing the necessary settings. An example of such a configuration, +following up to the previous example, may be:

+
---
+name: AIMM
+log:
+    disable_existing_loggers: false
+    formatters:
+        default: {}
+    handlers:
+        console:
+            class : logging.StreamHandler
+            level   : INFO
+            stream  : ext://sys.stdout
+    root:
+        handlers:
+        - console
+        level: INFO
+    version: 1
+engine:
+    sigterm_timeout: 5
+    max_children: 5
+    check_children_period: 3
+backend:
+    module: aimm.server.backend.sqlite
+    path: ./data/aimm.db
+control:
+  - module: aimm.server.control.repl
+    server:
+        host: 0.0.0.0
+        port: 9999
+    users:
+        - username: user
+          password: d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1
+plugins:
+    names:
+        - 'plugins.sklearn_wrapper'
+...
+
+
+

For more details on configuring and running the server, consult the +documentation entry.

+

Running the server now starts a service that listens on the address +127.0.0.1:9999 and allows clients to connect, create, fit and use SVC +models. Since the only configured control is REPL control, it should be +possible to connect using the REPL client. Following +snippet shows how the library may be used:

+
from aimm.client import repl
+
+async def run():
+    aimm = repl.AIMM()
+    await aimm.connect('ws://127.0.0.1:9999/ws')
+    m = await aimm.create_instance('plugins.sklearn.SVC')
+
+    await m.fit(repl.DataAccessArg('iris_inputs'), repl.DataAccessArg('iris_outputs'))
+    await m.predict(repl.DataAccessArg('iris_inputs'))
+
+
+

The code would connect to the server, create an SVC instance, remotely fit it +with Iris data and use it to perform classification.

+

Example of Hat integration is available in the examples directory.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/index.html b/build/docs/index.html new file mode 100644 index 0000000..b917141 --- /dev/null +++ b/build/docs/index.html @@ -0,0 +1,126 @@ + + + + + + + Content — aimm documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Content

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/introduction.html b/build/docs/introduction.html new file mode 100644 index 0000000..fa8cf90 --- /dev/null +++ b/build/docs/introduction.html @@ -0,0 +1,163 @@ + + + + + + + Introduction — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Introduction

+

The Artificial Intelligence Model Manager (AIMM) project aims to provide +resources for management of computational intelligence models. Using a +plugin-based approach, it provides a services capable of:

+
+
    +
  • creating and storing models

  • +
  • fitting models

  • +
  • upload of already fitted models

  • +
  • data access

  • +
  • running the models

  • +
+
+

The server also has support for changeable frontend and persistence interfaces. +This allows users to implement the ways server communicates to its clients +(multiple parallel interfaces are supported) or stores the models. There are +also default interfaces that are supported for both of these functions.

+
+

Installation

+

AIMM is a Python (3.12 and newer) package containing implementations of the +server implementation and some of its clients. It can be installed with the +following command:

+
pip install aimm
+
+
+
+
+

Development environment

+

Development environment includes, besides the standard requirements of the base +AIMM package, various tools and libraries that are used for the build process, +documentation and testing. To set up the development environment, Python 3.12 +and poetry are needed. Recommended way to set up is by running:

+
python -m venv venv
+source venv/bin/activate
+pip install poetry
+poetry install
+
+
+

All other generic tasks like testing and documentation building are done +through the build tool, use doit list to preview the complete list of all +available tasks.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/objects.inv b/build/docs/objects.inv new file mode 100644 index 0000000..b3eba75 Binary files /dev/null and b/build/docs/objects.inv differ diff --git a/build/docs/plugins.html b/build/docs/plugins.html new file mode 100644 index 0000000..331b53b --- /dev/null +++ b/build/docs/plugins.html @@ -0,0 +1,432 @@ + + + + + + + Plugins — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Plugins

+

AIMM system relies on user-written plugins for implementations of machine +learning models, preprocessing and data-fetching methods. Both server and its +clients may use the plugin API. The plugins are implemented as Python modules +that are dynamically imported at some point during servers or clients runtime. +Within these modules, plugin implementators should use the decorators from the +aimm.plugins module to indicate which functions and classes are entry +points to various machine learning workflow actions. They may also, through +decorator arguments, pass information to plugin callers, allowing them to +further declare semantics of some of their arguments (e.g. declare that an +argument should receive a callback function for progress notification).

+

Before accessing any plugins, they need to be imported and initialized. One way +to do this (other then importing them manually) is by calling the +initialization function:

+
+
+aimm.plugins.initialize(conf: Dict)
+

Imports the plugin modules, registering the entry point functions

+
+
Parameters:
+

conf – configuration that follows schema under id +aimm://plugins/schema.yaml#

+
+
+
+ +

Initialize configuration schema:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://plugins.yaml#'
+type: object
+required:
+    - names
+properties:
+    names:
+        type: array
+        items:
+            type: string
+            description: Python module name
+...
+
+
+
+

Warning

+

The AIMM plugin interface does not create a sandbox environment +when executing plugins, it is up to the implementator to make sure not to +perform unsafe actions

+
+
+

Decorators

+
+
+@aimm.plugins.data_access(name: str, state_cb_arg_name: str | None = None) Callable
+

Decorator used to indicate that the wrapped function is a data access +function. The decorated function can take any number of positional and +keyword arguments and should return the accessed data.

+
+
Parameters:
+
    +
  • name – name of the data access type

  • +
  • state_cb_arg_name – if set, indicates that the caller should pass a +state callback function as a keyword argument and use the passed +value as the argument name. The function is of type Callable[Any], +where the only argument is JSON serializable data.

  • +
+
+
Returns:
+

Decorated function

+
+
+
+ +
+
+@aimm.plugins.instantiate(model_type: str, state_cb_arg_name: str | None = None) Callable
+

Decorator used to indicate that the wrapped function is a model instance +creation function. The decorated function should take any number of +positional and keyword arguments and should return the newly created model +instance.

+
+
Parameters:
+
    +
  • model_type – name of the model type

  • +
  • state_cb_arg_name – if set, indicates that the caller should pass a +state callback function as a keyword argument and use the passed +value as the argument name. The function is of type Callable[Any].

  • +
+
+
Returns:
+

Decorated function

+
+
+
+ +
+
+@aimm.plugins.fit(model_types: List[str], state_cb_arg_name: str | None = None, instance_arg_name: str | None = None) Callable
+

Decorator used to indicate that the wrapped function is a fitting +function. The decorated function should take at least one argument - model +instance (passed as the first positional argument by default). It may also +take any number of additional positional and keyword arguments and should +return the updated model instance.

+
+
Parameters:
+
    +
  • model_types – types of models supported by the decorated function

  • +
  • state_cb_arg_name – if set, indicates that the caller should pass a +state callback function as a keyword argument and use the passed +value as the argument name. The function is of type Callable[Any]

  • +
  • instance_arg_name – if set, indicates under which +argument name to pass the concrete model instance. If not set, it +is passed in the first positional argument

  • +
+
+
Returns:
+

Decorated function

+
+
+
+ +
+
+@aimm.plugins.predict(model_types: List[str], state_cb_arg_name: str | None = None, instance_arg_name: str | None = None) Callable
+

Decorator used to indicate that the wrapped function is a prediction +function. The decorated function should take at least one argument - model +instance (passed as the first positional argument by default). It may also +take any number of additional positional and keyword arguments and should +return the updated model instance.

+
+
Parameters:
+
    +
  • model_types – types of models supported by the decorated function

  • +
  • state_cb_arg_name – if set, indicates that the caller should pass a +state callback function as a keyword argument and use the passed +value as the argument name. The function is of type Callable[Any]

  • +
  • instance_arg_name – if set, indicates under which argument name to pass +the concrete model instance. If not set, it is passed in the first +positional argument

  • +
+
+
Returns:
+

Decorated function

+
+
+
+ +
+
+@aimm.plugins.serialize
+

Decorator used to indicate that the wrapped function is a serialize +function. The decorated function should have the following signature:

+

(instance: Any) -> ByteString

+

The return value is the byte representation of the model instance.

+
+
Parameters:
+

model_types – types of models supported by the decorated function

+
+
Returns:
+

Decorated function

+
+
+
+ +
+
+@aimm.plugins.deserialize
+

Decorator used to indicate that the wrapped function is a +deserialize function. The decorated function should have the following +signature:

+

(instance_bytes: ByteString) -> Any

+

The return value is the deserialized model instance.

+
+
Parameters:
+

model_types – types of models supported by the decorated function

+
+
Returns:
+

Decorated function

+
+
+
+ +

It is common for a model type to have all of the above defined functions, for +this reason the following class and decorator are introduced:

+
+
+class aimm.plugins.Model
+

Interface unifying multiple plugin entry points under same type. +__init__ method is treated as instantiation function.

+
+
+abstract fit(*args: Any, **kwargs: Any) Any
+

Fit method for model instances

+
+ +
+
+abstract predict(*args: Any, **kwargs: Any) Any
+

Predict method for model instances

+
+ +
+
+abstract serialize() ByteString
+

Serialize method for model instances

+
+ +
+
+abstract classmethod deserialize(instance_bytes: ByteString) Model
+

Deserialize method for model instances

+
+ +
+ +
+
+aimm.plugins.model(cls: Type) Type
+

Model class decorator, used to mark that a class may be used as a model +implementation. Model class unifies different plugin actions +(instantiate() as __init__, fit(), predict(), +serialize(), deserialize() as their same-named class methods) +under the same model type (module + class name). The class should implement +the Model interface.

+
+
Parameters:
+

cls (Model) – model class

+
+
Returns:
+

Decorated class

+
+
+
+ +
+
+

Calling plugins

+

After implementing and loading plugins, they can be called using following +functions:

+
+
+aimm.plugins.exec_data_access(name: str, state_cb: ~typing.Callable[[~typing.Dict], None] = <function <lambda>>, *args: ~typing.Any, **kwargs: ~typing.Any) Any
+

Uses a loaded plugin to access data

+
+ +
+
+aimm.plugins.exec_instantiate(model_type: str, state_cb: ~typing.Callable[[~typing.Dict], None] = <function <lambda>>, *args: ~typing.Any, **kwargs: ~typing.Any) Any
+

Uses a loaded plugin to create a model instance

+
+ +
+
+aimm.plugins.exec_fit(model_type: str, instance: ~typing.Any, state_cb: ~typing.Callable[[~typing.Dict], None] = <function <lambda>>, *args: ~typing.Any, **kwargs: ~typing.Any) Any
+

Uses a loaded plugin to fit a model instance

+
+ +
+
+aimm.plugins.exec_predict(model_type: str, instance: ~typing.Any, state_cb: ~typing.Callable[[~typing.Dict], None] = <function <lambda>>, *args: ~typing.Any, **kwargs: ~typing.Any) tuple[Any, Any]
+

Uses a loaded plugin to perform a prediction with a given model +instance. Also returns the instance because it might be altered during the +prediction, e.g. with reinforcement learning models.

+
+ +
+
+aimm.plugins.exec_serialize(model_type: str, instance: Any) ByteString
+

Uses a loaded plugin to convert model into bytes

+
+ +
+
+aimm.plugins.exec_deserialize(model_type: str, instance_bytes: ByteString) Any
+

Uses a loaded plugin to convert bytes into a model instance

+
+ +

State callbacks have the following signature:

+
+
+aimm.plugins.StateCallback
+

Generic state callback function signature a plugin would receive

+

alias of Callable[[Dict], None]

+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/py-modindex.html b/build/docs/py-modindex.html new file mode 100644 index 0000000..077f9c1 --- /dev/null +++ b/build/docs/py-modindex.html @@ -0,0 +1,134 @@ + + + + + + Python Module Index — aimm documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ a +
+ + + + + + + + + + +
 
+ a
+ aimm +
    + aimm.client.repl +
+ + +
+
+
+ +
+ +
+

© Copyright 2024, Zlatan Sičanica.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/search.html b/build/docs/search.html new file mode 100644 index 0000000..d7f9783 --- /dev/null +++ b/build/docs/search.html @@ -0,0 +1,129 @@ + + + + + + Search — aimm documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2024, Zlatan Sičanica.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/build/docs/searchindex.js b/build/docs/searchindex.js new file mode 100644 index 0000000..420b410 --- /dev/null +++ b/build/docs/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Action requests and states": [[8, "action-requests-and-states"]], "Actions": [[10, "actions"]], "Add instance": [[8, "add-instance"]], "Architecture": [[12, "architecture"]], "Argument preprocessing": [[10, "argument-preprocessing"]], "Backend": [[7, null]], "Calling plugins": [[6, "calling-plugins"]], "Cancel": [[8, "cancel"]], "Citing AIMM": [[0, null]], "Clients": [[1, null]], "Common data structures": [[12, "common-data-structures"]], "Components": [[12, "components"]], "Configuration": [[12, "configuration"]], "Content": [[4, null]], "Control": [[9, null]], "Create instance": [[8, "create-instance"]], "Decorators": [[6, "decorators"]], "Development environment": [[5, "development-environment"]], "Engine": [[11, null]], "Event": [[7, "event"], [8, null]], "Fit": [[8, "fit"]], "Getting started": [[3, null]], "Hat": [[12, "hat"]], "Installation": [[5, "installation"]], "Introduction": [[5, null]], "JSON representations": [[10, "json-representations"]], "Multiprocessing": [[11, "multiprocessing"]], "Plugins": [[6, null]], "Predict": [[8, "predict"]], "REPL": [[2, null], [10, null]], "RPC interface": [[10, "rpc-interface"]], "SQLite": [[7, "sqlite"]], "Server": [[12, null]], "State": [[8, "state"], [10, "state"], [11, "state"]], "Update instance": [[8, "update-instance"]], "Usage": [[12, "usage"]], "add_instance": [[10, "add-instance"]], "create_instance": [[10, "create-instance"]], "fit": [[10, "fit"]], "login": [[10, "login"]], "logout": [[10, "logout"]], "predict": [[10, "predict"]], "update_instance": [[10, "update-instance"]]}, "docnames": ["cite", "clients/index", "clients/repl", "getting_started", "index", "introduction", "plugins", "server/backend", "server/control/event", "server/control/index", "server/control/repl", "server/engine", "server/index"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["cite.rst", "clients/index.rst", "clients/repl.rst", "getting_started.rst", "index.rst", "introduction.rst", "plugins.rst", "server/backend.rst", "server/control/event.rst", "server/control/index.rst", "server/control/repl.rst", "server/engine.rst", "server/index.rst"], "indexentries": {"action (class in aimm.server.common)": [[11, "aimm.server.common.Action", false]], "add_instance() (aimm.client.repl.aimm method)": [[2, "aimm.client.repl.AIMM.add_instance", false]], "add_instance() (aimm.server.common.engine method)": [[11, "aimm.server.common.Engine.add_instance", false]], "address (aimm.client.repl.aimm property)": [[2, "aimm.client.repl.AIMM.address", false]], "aimm (class in aimm.client.repl)": [[2, "aimm.client.repl.AIMM", false]], "aimm.client.repl": [[2, "module-aimm.client.repl", false]], "args (aimm.client.repl.dataaccessarg attribute)": [[2, "aimm.client.repl.DataAccessArg.args", false]], "args (aimm.server.common.dataaccess attribute)": [[12, "aimm.server.common.DataAccess.args", false]], "async_group (aimm.client.repl.aimm property)": [[2, "aimm.client.repl.AIMM.async_group", false]], "async_group (aimm.server.mprocess.processmanager property)": [[11, "aimm.server.mprocess.ProcessManager.async_group", false]], "backend (class in aimm.server.common)": [[7, "aimm.server.common.Backend", false]], "connect() (aimm.client.repl.aimm method)": [[2, "aimm.client.repl.AIMM.connect", false]], "control (class in aimm.server.common)": [[9, "aimm.server.common.Control", false]], "create_backend() (in module aimm.server.common)": [[7, "aimm.server.common.create_backend", false]], "create_control() (in module aimm.server.common)": [[9, "aimm.server.common.create_control", false]], "create_handler() (aimm.server.mprocess.processmanager method)": [[11, "aimm.server.mprocess.ProcessManager.create_handler", false]], "create_instance() (aimm.client.repl.aimm method)": [[2, "aimm.client.repl.AIMM.create_instance", false]], "create_instance() (aimm.server.common.engine method)": [[11, "aimm.server.common.Engine.create_instance", false]], "create_model() (aimm.server.common.backend method)": [[7, "aimm.server.common.Backend.create_model", false]], "create_subscription() (in module aimm.server.common)": [[7, "aimm.server.common.create_subscription", false]], "createsubscription (in module aimm.server.common)": [[12, "aimm.server.common.CreateSubscription", false]], "data_access() (in module aimm.plugins)": [[6, "aimm.plugins.data_access", false]], "dataaccess (class in aimm.server.common)": [[12, "aimm.server.common.DataAccess", false]], "dataaccessarg (class in aimm.client.repl)": [[2, "aimm.client.repl.DataAccessArg", false]], "deserialize() (aimm.plugins.model class method)": [[6, "aimm.plugins.Model.deserialize", false]], "deserialize() (in module aimm.plugins)": [[6, "aimm.plugins.deserialize", false]], "engine (class in aimm.server.common)": [[11, "aimm.server.common.Engine", false]], "exec_data_access() (in module aimm.plugins)": [[6, "aimm.plugins.exec_data_access", false]], "exec_deserialize() (in module aimm.plugins)": [[6, "aimm.plugins.exec_deserialize", false]], "exec_fit() (in module aimm.plugins)": [[6, "aimm.plugins.exec_fit", false]], "exec_instantiate() (in module aimm.plugins)": [[6, "aimm.plugins.exec_instantiate", false]], "exec_predict() (in module aimm.plugins)": [[6, "aimm.plugins.exec_predict", false]], "exec_serialize() (in module aimm.plugins)": [[6, "aimm.plugins.exec_serialize", false]], "fit() (aimm.client.repl.aimm method)": [[2, "aimm.client.repl.AIMM.fit", false]], "fit() (aimm.client.repl.model method)": [[2, "aimm.client.repl.Model.fit", false]], "fit() (aimm.plugins.model method)": [[6, "aimm.plugins.Model.fit", false]], "fit() (aimm.server.common.engine method)": [[11, "aimm.server.common.Engine.fit", false]], "fit() (in module aimm.plugins)": [[6, "aimm.plugins.fit", false]], "get_models() (aimm.server.common.backend method)": [[7, "aimm.server.common.Backend.get_models", false]], "initialize() (in module aimm.plugins)": [[6, "aimm.plugins.initialize", false]], "instance (aimm.server.common.model attribute)": [[12, "aimm.server.common.Model.instance", false]], "instance_id (aimm.server.common.model attribute)": [[12, "aimm.server.common.Model.instance_id", false]], "instantiate() (in module aimm.plugins)": [[6, "aimm.plugins.instantiate", false]], "kwargs (aimm.client.repl.dataaccessarg attribute)": [[2, "aimm.client.repl.DataAccessArg.kwargs", false]], "kwargs (aimm.server.common.dataaccess attribute)": [[12, "aimm.server.common.DataAccess.kwargs", false]], "model (class in aimm.client.repl)": [[2, "aimm.client.repl.Model", false]], "model (class in aimm.plugins)": [[6, "aimm.plugins.Model", false]], "model (class in aimm.server.common)": [[12, "aimm.server.common.Model", false]], "model() (in module aimm.plugins)": [[6, "aimm.plugins.model", false]], "model_type (aimm.server.common.model attribute)": [[12, "aimm.server.common.Model.model_type", false]], "module": [[2, "module-aimm.client.repl", false]], "name (aimm.client.repl.dataaccessarg attribute)": [[2, "aimm.client.repl.DataAccessArg.name", false]], "name (aimm.server.common.dataaccess attribute)": [[12, "aimm.server.common.DataAccess.name", false]], "pluginarg (in module aimm.client.repl)": [[2, "aimm.client.repl.PluginArg", false]], "predict() (aimm.client.repl.aimm method)": [[2, "aimm.client.repl.AIMM.predict", false]], "predict() (aimm.client.repl.model method)": [[2, "aimm.client.repl.Model.predict", false]], "predict() (aimm.plugins.model method)": [[6, "aimm.plugins.Model.predict", false]], "predict() (aimm.server.common.engine method)": [[11, "aimm.server.common.Engine.predict", false]], "predict() (in module aimm.plugins)": [[6, "aimm.plugins.predict", false]], "process_events() (aimm.server.common.backend method)": [[7, "aimm.server.common.Backend.process_events", false]], "process_events() (aimm.server.common.control method)": [[9, "aimm.server.common.Control.process_events", false]], "processmanager (class in aimm.server.mprocess)": [[11, "aimm.server.mprocess.ProcessManager", false]], "register_model_change_cb() (aimm.server.common.backend method)": [[7, "aimm.server.common.Backend.register_model_change_cb", false]], "serialize() (aimm.plugins.model method)": [[6, "aimm.plugins.Model.serialize", false]], "serialize() (in module aimm.plugins)": [[6, "aimm.plugins.serialize", false]], "state (aimm.client.repl.aimm property)": [[2, "aimm.client.repl.AIMM.state", false]], "state (aimm.server.common.engine property)": [[11, "aimm.server.common.Engine.state", false]], "statecallback (in module aimm.plugins)": [[6, "aimm.plugins.StateCallback", false]], "subscribe_to_state_change() (aimm.server.common.engine method)": [[11, "aimm.server.common.Engine.subscribe_to_state_change", false]], "update_instance() (aimm.client.repl.aimm method)": [[2, "aimm.client.repl.AIMM.update_instance", false]], "update_instance() (aimm.server.common.engine method)": [[11, "aimm.server.common.Engine.update_instance", false]], "update_model() (aimm.server.common.backend method)": [[7, "aimm.server.common.Backend.update_model", false]], "wait_result() (aimm.server.common.action method)": [[11, "aimm.server.common.Action.wait_result", false]]}, "objects": {"aimm.client": [[2, 0, 0, "-", "repl"]], "aimm.client.repl": [[2, 1, 1, "", "AIMM"], [2, 1, 1, "", "DataAccessArg"], [2, 1, 1, "", "Model"], [2, 5, 1, "", "PluginArg"]], "aimm.client.repl.AIMM": [[2, 2, 1, "", "add_instance"], [2, 3, 1, "", "address"], [2, 3, 1, "", "async_group"], [2, 2, 1, "", "connect"], [2, 2, 1, "", "create_instance"], [2, 2, 1, "", "fit"], [2, 2, 1, "", "predict"], [2, 3, 1, "", "state"], [2, 2, 1, "", "update_instance"]], "aimm.client.repl.DataAccessArg": [[2, 4, 1, "", "args"], [2, 4, 1, "", "kwargs"], [2, 4, 1, "", "name"]], "aimm.client.repl.Model": [[2, 2, 1, "", "fit"], [2, 2, 1, "", "predict"]], "aimm.plugins": [[6, 1, 1, "", "Model"], [6, 5, 1, "", "StateCallback"], [6, 6, 1, "", "data_access"], [6, 6, 1, "", "deserialize"], [6, 6, 1, "", "exec_data_access"], [6, 6, 1, "", "exec_deserialize"], [6, 6, 1, "", "exec_fit"], [6, 6, 1, "", "exec_instantiate"], [6, 6, 1, "", "exec_predict"], [6, 6, 1, "", "exec_serialize"], [6, 6, 1, "", "fit"], [6, 6, 1, "", "initialize"], [6, 6, 1, "", "instantiate"], [6, 6, 1, "", "model"], [6, 6, 1, "", "predict"], [6, 6, 1, "", "serialize"]], "aimm.plugins.Model": [[6, 2, 1, "", "deserialize"], [6, 2, 1, "", "fit"], [6, 2, 1, "", "predict"], [6, 2, 1, "", "serialize"]], "aimm.server.common": [[11, 1, 1, "", "Action"], [7, 1, 1, "", "Backend"], [9, 1, 1, "", "Control"], [12, 5, 1, "", "CreateSubscription"], [12, 1, 1, "", "DataAccess"], [11, 1, 1, "", "Engine"], [12, 1, 1, "", "Model"], [7, 6, 1, "", "create_backend"], [9, 6, 1, "", "create_control"], [7, 6, 1, "", "create_subscription"]], "aimm.server.common.Action": [[11, 2, 1, "", "wait_result"]], "aimm.server.common.Backend": [[7, 2, 1, "", "create_model"], [7, 2, 1, "", "get_models"], [7, 2, 1, "", "process_events"], [7, 2, 1, "", "register_model_change_cb"], [7, 2, 1, "", "update_model"]], "aimm.server.common.Control": [[9, 2, 1, "", "process_events"]], "aimm.server.common.DataAccess": [[12, 4, 1, "", "args"], [12, 4, 1, "", "kwargs"], [12, 4, 1, "", "name"]], "aimm.server.common.Engine": [[11, 2, 1, "", "add_instance"], [11, 2, 1, "", "create_instance"], [11, 2, 1, "", "fit"], [11, 2, 1, "", "predict"], [11, 3, 1, "", "state"], [11, 2, 1, "", "subscribe_to_state_change"], [11, 2, 1, "", "update_instance"]], "aimm.server.common.Model": [[12, 4, 1, "", "instance"], [12, 4, 1, "", "instance_id"], [12, 4, 1, "", "model_type"]], "aimm.server.mprocess": [[11, 1, 1, "", "ProcessManager"]], "aimm.server.mprocess.ProcessManager": [[11, 3, 1, "", "async_group"], [11, 2, 1, "", "create_handler"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "property", "Python property"], "4": ["py", "attribute", "Python attribute"], "5": ["py", "data", "Python data"], "6": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:property", "4": "py:attribute", "5": "py:data", "6": "py:function"}, "terms": {"": [3, 7, 8, 9, 10, 11, 12], "0": 3, "001": 3, "1": 3, "10": 0, "100": 3, "1109": 0, "12": 5, "127": 3, "2022": 0, "256": 2, "3": [3, 5], "30414": 0, "30426": 0, "3159715": 0, "5": 3, "9734070": 0, "9999": 3, "A": 12, "For": 3, "If": [0, 2, 6, 10, 11, 12], "In": [7, 9], "It": [5, 6, 7, 10, 11, 12], "Its": [2, 10, 11], "One": 6, "The": [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12], "There": 5, "These": [1, 10, 11], "To": [5, 11, 12], "__init__": [3, 6], "_nestedsequ": 2, "_supportsarrai": 2, "_svc": 3, "abl": 11, "about": 10, "abov": [6, 10], "abstract": [6, 7, 11], "access": [0, 2, 3, 5, 6, 7, 8, 10, 11, 12], "accessing_data": 11, "action": [6, 11, 12], "action_state_event_typ": 8, "action_state_typ": 8, "activ": [5, 11], "actor": 9, "actual": [3, 11], "ad": 10, "add": [2, 11], "add_inst": [1, 2, 8, 11], "add_instance_prefix": 8, "addit": [6, 10], "addition": [11, 12], "address": [1, 2, 3, 10], "advanc": 10, "advis": 10, "after": [3, 6, 10, 11, 12], "against": 12, "aim": 5, "aimm": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "aio": 11, "alia": [2, 6, 12], "aliv": 11, "all": [5, 6, 7, 9, 11, 12], "allow": [3, 5, 6, 7, 10, 11, 12], "along": 10, "alreadi": [5, 11], "also": [0, 5, 6, 7, 9, 10, 11, 12], "alter": 6, "amount": 11, "an": [0, 2, 3, 6, 7, 8, 10, 11, 12], "ani": [2, 6, 7, 8, 9, 10, 11, 12], "api": [3, 6], "applic": [1, 12], "approach": 5, "appropri": 10, "ar": [1, 2, 3, 5, 6, 7, 8, 10, 11, 12], "architectur": 0, "arg": [1, 2, 6, 8, 10, 11, 12], "argument": [2, 6, 7, 8, 9, 11, 12], "around": 3, "arrai": [6, 7, 8, 9, 10, 11], "articl": 0, "artifici": [0, 5], "assign": 11, "async": [2, 3, 7, 9, 11], "async_clos": 11, "async_group": [1, 2, 11], "asynchron": 11, "asyncio": 11, "attempt": 12, "author": [0, 10], "avail": [0, 1, 2, 3, 5, 7], "avoid": 11, "await": 3, "backend": [3, 9, 11, 12], "base": [0, 5], "base64": [7, 8, 10], "basicconfig": 12, "becaus": 6, "becom": 3, "befor": [2, 6, 10, 11], "being": [2, 10, 11], "besid": 5, "bibtex": 0, "bin": 5, "blob": 7, "block": 11, "bomb": 11, "bool": 2, "boolean": 8, "bori": 0, "both": [5, 6], "bu": 8, "buffer": 2, "build": 5, "byte": [2, 6], "bytestr": 6, "c": 3, "call": [2, 7, 8, 9, 10, 11, 12], "callabl": [6, 7, 11, 12], "callback": [6, 7, 11], "caller": [6, 7, 11], "can": [0, 1, 5, 6, 7, 8, 11, 12], "cancel": 11, "cancel_prefix": 8, "cancellederror": 11, "cannot": 12, "capabl": 5, "case": [10, 11], "caus": 10, "cb": [7, 11], "central": 11, "certain": 11, "chang": [2, 7, 8, 9, 11, 12], "changeabl": 5, "chapter": 1, "charg": 11, "check": 11, "check_children_period": [3, 11], "child": 11, "children": 11, "cite": 4, "cl": [3, 6], "class": [2, 3, 6, 7, 9, 11, 12], "classif": 3, "classmethod": [3, 6], "cli": 3, "client": [2, 3, 4, 5, 6, 7, 9, 10, 12], "code": 3, "collect": 9, "combin": 12, "command": [5, 12], "common": [6, 7, 8, 9, 10, 11], "commun": [1, 2, 5, 8, 9, 12], "complet": [5, 11], "complex": [2, 12], "compon": [0, 11], "comput": 5, "concret": [6, 7, 9], "concurr": 11, "condit": 11, "conf": [6, 7, 9, 12], "configur": [3, 6, 7, 8, 9, 10, 11], "connect": [1, 2, 3, 7, 10, 12], "consid": 0, "consider": 10, "consist": [9, 11, 12], "consol": 3, "const": [8, 10], "constructor": 11, "consult": 3, "contain": [2, 3, 5, 7, 8, 9, 10, 11, 12], "control": [1, 2, 3, 7, 8, 10, 11, 12], "convers": 10, "convert": [6, 8, 10], "copi": [8, 10], "correl": 12, "correspond": 10, "could": 11, "count": 11, "creat": [2, 3, 5, 6, 7, 9, 11], "create_backend": 7, "create_backend_subscript": [7, 9], "create_control": 9, "create_handl": 11, "create_inst": [1, 2, 3, 8, 11], "create_instance_prefix": 8, "create_model": 7, "create_subscript": [7, 9, 12], "createsubscript": 12, "creation": [6, 8, 11], "current": [2, 10], "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1": 3, "data": [2, 3, 5, 6, 7, 8, 10, 11], "data_access": [3, 6, 8, 10, 11], "dataaccess": [8, 10, 11, 12], "dataaccessarg": [1, 2, 3], "databas": [7, 11], "datafram": [2, 10], "dataset": [3, 11], "db": 3, "declar": 6, "def": 3, "default": [3, 5, 6, 7, 9, 10, 12], "defin": [3, 6, 7, 10, 12], "definit": 3, "depend": [7, 8], "descirpt": 10, "describ": 10, "descript": [6, 7, 8, 10, 11], "deseri": [3, 6, 7], "detail": [3, 11, 12], "develop": 10, "diagram": 12, "dict": [2, 6, 7, 9, 10, 11, 12], "dictionari": [7, 8, 11], "differ": [6, 11, 12], "directli": [2, 8, 10, 11, 12], "directori": 3, "disable_existing_logg": 3, "disallow": 11, "do": 6, "docstr": [11, 12], "document": [1, 3, 5, 10, 12], "doe": [6, 7, 11], "doesn": 11, "doi": 0, "doit": 5, "done": [5, 8, 11], "driven": 0, "dtype": [2, 10], "dump": 3, "dure": [6, 11], "dynam": [3, 6, 7, 9, 12], "e": [6, 11], "either": 8, "empti": 10, "encod": [7, 8, 10], "engin": [3, 8, 9, 10, 12], "entir": 12, "entri": [0, 3, 6, 9], "enum": [8, 10, 11], "environ": [2, 6], "etc": [11, 12], "event": [0, 9, 11, 12], "event_cli": [7, 9], "event_prefix": 8, "event_server_group": 12, "exact": [7, 8, 9, 11, 12], "exampl": 3, "exec_data_access": [6, 12], "exec_deseri": 6, "exec_fit": 6, "exec_instanti": [6, 12], "exec_predict": 6, "exec_seri": 6, "execut": [6, 8, 10, 11], "exist": [2, 11], "exit": 12, "expect": 11, "expir": 11, "explain": 12, "ext": 3, "extern": [9, 12], "fail": 8, "fals": 3, "fetch": 6, "field": 12, "file": [3, 7, 12], "first": [3, 6, 11], "fit": [1, 2, 3, 5, 6, 11], "fit_prefix": 8, "float": [2, 11], "fn": 11, "follow": [0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12], "forc": 11, "fork": 11, "forma": 10, "formatt": 3, "from": [2, 3, 6, 7, 8, 9, 10], "frontend": 5, "full": 10, "function": [2, 5, 6, 7, 8, 9, 10, 11, 12], "further": 6, "g": [6, 11], "gamma": 3, "gener": [2, 5, 6, 8, 9], "get": [4, 7], "get_model": 7, "get_subscript": 12, "give": 10, "given": [6, 7, 9, 10, 12], "global": 12, "group": [2, 11, 12], "h": 12, "ha": [5, 8, 10, 11, 12], "halt": 12, "handl": [11, 12], "handler": [3, 11], "hash": 2, "hat": [3, 7, 8, 10, 11], "have": [6, 7, 8, 9, 10, 11, 12], "help": 12, "here": [0, 10, 12], "hereaft": 2, "hold": 11, "host": [3, 10], "how": [3, 11], "http": [6, 7, 8, 9, 10, 11, 12], "i": [0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12], "id": [6, 7, 8, 9, 10, 11, 12], "identifi": 12, "ieee": 0, "ignor": [7, 9], "implement": [2, 3, 5, 6, 7, 9, 10, 11, 12], "import": [2, 3, 6, 7, 9, 12], "in_progress": 8, "includ": 5, "incom": 8, "indic": [6, 11, 12], "info": 3, "inform": [3, 6, 8], "infrastructur": [7, 12], "initi": [1, 6, 10, 11, 12], "input": [3, 11], "instal": [3, 12], "instanc": [2, 3, 6, 7, 9, 10, 11, 12], "instance_arg_nam": 6, "instance_byt": [3, 6], "instance_id": [2, 7, 8, 10, 11, 12], "instanti": [6, 11], "instead": [2, 7, 11], "int": [2, 10, 11, 12], "integ": [8, 10, 11], "integr": [3, 7, 9, 12], "intellig": [0, 5], "intend": 2, "interact": 2, "interfac": [0, 2, 3, 5, 6, 7, 9, 11, 12], "intern": 11, "interpret": 11, "introduc": 6, "introduct": 4, "involv": 3, "iri": 3, "iris_input": 3, "iris_output": 3, "item": [6, 7, 8, 9, 10], "iter": 12, "its": [0, 2, 3, 5, 6, 7, 8, 10, 11, 12], "journal": 0, "json": [2, 6, 7, 8, 9, 11, 12], "juggler": 10, "just": 10, "keep": [11, 12], "kei": 11, "keyword": [2, 6, 10, 11, 12], "kind": [7, 11], "kwarg": [1, 2, 6, 8, 10, 11, 12], "lambda": 6, "later": 11, "learn": 6, "least": 6, "level": [3, 10], "librari": [1, 2, 3, 5], "lifetim": 11, "like": 5, "limit": [7, 11], "line": 12, "list": [2, 5, 6, 7, 9, 10, 11, 12], "listen": [3, 10, 11, 12], "load": [3, 6], "load_iri": 3, "local": 10, "log": [3, 7, 9, 10, 12], "login": 2, "look": 9, "loop": 11, "m": [3, 5], "machin": 6, "made": 11, "mai": [3, 6, 8, 9, 10, 11, 12], "main": [3, 7, 9, 10, 11, 12], "make": [6, 7, 11], "manag": [0, 2, 5, 11, 12], "mandatori": 12, "manual": 6, "map": 2, "mark": 6, "match": [7, 8, 9], "max_children": [3, 11], "maximum": 11, "mention": 10, "messag": [10, 12], "meta": 11, "metadata": 12, "method": [2, 6, 8, 10, 11, 12], "might": [6, 10], "mila\u0161inovi\u0107": 0, "minim": [2, 7, 12], "model": [0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12], "model_prefix": 7, "model_typ": [2, 6, 7, 8, 10, 11, 12], "modifi": 11, "modul": [2, 3, 6, 7, 9, 11, 12], "monitor": 12, "more": [3, 10, 12], "mostli": 11, "mprocess": 11, "multipl": [3, 5, 6, 9], "multiprocess": 12, "name": [1, 2, 3, 6, 7, 8, 9, 10, 11, 12], "natur": 11, "neccessari": 12, "necessari": [3, 12], "need": [5, 6, 7, 8, 9, 10, 11, 12], "network": 12, "never": 11, "new": [2, 7, 11], "newer": 5, "newli": 6, "next": 3, "none": [2, 6, 7, 8, 9, 11], "notif": [6, 11], "notifi": [9, 11, 12], "now": 3, "null": 11, "number": [0, 6, 11], "numpi": 10, "numpy_arrai": 10, "object": [2, 6, 7, 8, 9, 10, 11, 12], "object_arg": [8, 10], "offer": 7, "often": 11, "old": [7, 11], "one": [2, 6, 7, 8, 9, 11, 12], "oneof": [8, 10, 11], "onli": [3, 6, 7, 9, 11, 12], "oper": 12, "optim": 10, "option": [7, 9, 11, 12], "order": [7, 9, 12], "org": [6, 7, 8, 9, 10, 11, 12], "other": [5, 6, 10, 11, 12], "out": 10, "output": 3, "over": [8, 10], "packag": [1, 5, 7], "page": 0, "pair": 7, "panda": 10, "pandas_datafram": 10, "pandas_seri": 10, "paper": 0, "parallel": [5, 9], "paramet": [6, 7, 9, 10, 11, 12], "part": [11, 12], "pass": [2, 6, 8, 10, 11, 12], "password": [2, 3, 10], "path": [3, 7, 12], "patternproperti": [8, 10, 11], "payload": [7, 8], "pend": 11, "perform": [3, 6, 7, 8, 11, 12], "period": 11, "persist": [5, 7, 11, 12], "pickl": [3, 11], "pickleabl": 11, "pip": 5, "pipe": 11, "place": 11, "placehold": [7, 9], "pleas": 0, "plugin": [2, 3, 4, 5, 8, 10, 11, 12], "pluginarg": [1, 2], "poetri": 5, "point": [6, 9], "pool": 11, "port": [3, 10], "posit": [2, 6, 10, 11, 12], "possibl": [3, 11], "predict": [1, 2, 3, 6, 11], "predixt_prefix": 8, "prefix": [7, 8], "preprocess": [6, 8], "prevent": 11, "preview": 5, "previou": 3, "principl": 9, "prior": [8, 11, 12], "pro": 10, "proc_notify_state_chang": 11, "procedur": 10, "process": [5, 11], "process_ev": [7, 9], "processhandl": 11, "processmanag": 11, "progress": [6, 11], "project": 5, "prompt": 2, "properti": [2, 6, 7, 8, 9, 10, 11, 12], "protocol": [2, 10], "provid": [1, 2, 3, 5, 7, 10, 11, 12], "proxycli": 9, "pseudo": 11, "purpos": [1, 2, 3, 11], "python": [2, 5, 6, 7, 9], "queri": [8, 11], "rais": [7, 11], "ran": 11, "react": 11, "reaction": 8, "real": 2, "realiti": 11, "reason": 6, "receiv": [2, 6, 7, 8, 9, 10, 11, 12], "recommend": 5, "ref": [8, 10, 12], "refer": [0, 1, 8, 11], "reflect": 11, "regist": [6, 7, 8, 12], "register_model_change_cb": 7, "registercallbackhandl": [7, 11], "reinforc": 6, "reli": 6, "remain": 12, "remot": [2, 3, 10], "repl": [1, 3, 9], "replac": [2, 7], "report": [2, 8], "repres": [2, 8, 11, 12], "represent": [6, 8, 12], "request": [2, 11], "request_id": 8, "requir": [5, 6, 7, 8, 9, 10, 11, 12], "research": 0, "resourc": [5, 11], "respect": 9, "respond": 8, "respons": 8, "rest": 11, "restrict": 2, "result": [2, 8, 10, 11, 12], "retriev": [7, 12], "return": [3, 6, 7, 8, 9, 10, 11, 12], "return_x_i": 3, "root": [3, 12], "run": [3, 5, 9, 11, 12], "runtim": 6, "same": [6, 7, 8, 9, 11], "sandbox": 6, "satisfi": [7, 8, 9, 11], "scada": 0, "schema": [6, 7, 8, 9, 10, 11, 12], "second": 11, "section": [10, 12], "secur": 10, "see": 12, "self": 3, "semant": 6, "send": [2, 10, 11], "sent": 11, "separ": [10, 11, 12], "seri": [2, 10], "serial": [3, 6, 7, 10], "serializ": [6, 10], "serv": [9, 10, 11], "server": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "servic": [3, 5, 12], "set": [3, 5, 6, 7, 11, 12], "sever": [10, 12], "sha": 2, "should": [3, 6, 7, 8, 9, 11, 12], "show": [3, 12], "shutdown": 11, "side": 7, "sigkil": 11, "signal": 11, "signatur": [6, 7, 9], "signifi": 10, "sigterm": 11, "sigterm_timeout": [3, 11], "similar": [9, 11], "simplifi": 2, "sinc": [3, 11, 12], "si\u010danica": 0, "sklearn": 3, "sklearn_wrapp": 3, "snippet": 3, "so": 11, "some": [1, 5, 6, 10, 12], "sourc": [5, 7], "source_timestamp": 8, "spawn": 11, "special": 11, "specif": [2, 8, 10, 12], "specifi": [0, 2, 8, 12], "sqlite": 3, "standard": [5, 11], "start": [4, 8, 11], "state": [1, 2, 6, 9, 12], "state_cb": [6, 11], "state_cb_arg_nam": 6, "state_event_typ": 8, "state_typ": 8, "statecallback": 6, "statu": 8, "stdout": 3, "step": 3, "stjepan": 0, "store": [5, 7, 11, 12], "str": [2, 6, 7, 9, 10, 11, 12], "stream": 3, "streamhandl": 3, "strict": 11, "string": [6, 7, 8, 9, 10, 11, 12], "structur": [7, 8, 10, 11], "subact": 11, "subprocess": 11, "subscrib": [9, 11], "subscribe_to_state_chang": 11, "subscript": [7, 9, 11, 12], "success": [10, 12], "successfulli": 8, "support": [5, 6, 10, 11, 12], "sure": 6, "su\u010di\u0107": 0, "svc": 3, "svm": 3, "sy": 3, "synchron": 10, "system": [0, 6, 11, 12], "t": 11, "take": [6, 10], "task": [5, 11], "termin": 11, "test": 5, "thei": [6, 7, 8, 9, 10, 11, 12], "theirs": 11, "them": [6, 10, 11, 12], "themselv": 11, "thi": [1, 2, 3, 5, 6, 8, 10, 11, 12], "through": [5, 6, 11], "time": 11, "timeout": 11, "timestamp": 7, "titl": 0, "to_dict": 10, "tolist": 10, "tool": 5, "transfer": 10, "translat": 10, "treat": 6, "trigger": 2, "true": [3, 8], "tupl": [6, 7, 9], "two": [7, 11], "type": [6, 7, 8, 9, 10, 11, 12], "under": [6, 11], "unifi": 6, "unsaf": 6, "until": [11, 12], "up": [3, 5, 6], "updat": [6, 10, 11], "update_inst": [1, 2, 8, 11], "update_instance_prefix": 8, "update_model": 7, "upload": 5, "upon": 8, "us": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12], "user": [2, 3, 5, 6, 10], "usernam": [3, 10], "valid": 12, "valu": [6, 8, 11, 12], "variou": [5, 6, 12], "venv": 5, "version": 3, "volum": 0, "w": [3, 10], "wa": [8, 11], "wai": [5, 6], "wait": [11, 12], "wait_result": 11, "warn": [7, 9], "web": 12, "websocket": 10, "well": 11, "when": [6, 7, 9, 11, 12], "whenev": 11, "where": [6, 7, 10], "which": [2, 6, 7, 8, 9, 10, 11, 12], "while": [2, 11], "whose": 11, "within": [2, 6], "work": [9, 10, 11, 12], "worker": 11, "workflow": [6, 11], "would": [3, 6, 7, 9, 11], "wrap": [6, 11], "wrapper": 3, "write": 3, "written": 6, "x": 3, "xdg_config_hom": 12, "y": 3, "yaml": [3, 6, 7, 8, 9, 10, 11, 12], "year": 0, "you": 0, "your": 0, "zlatan": 0}, "titles": ["Citing AIMM", "Clients", "REPL", "Getting started", "Content", "Introduction", "Plugins", "Backend", "Event", "Control", "REPL", "Engine", "Server"], "titleterms": {"action": [8, 10], "add": 8, "add_inst": 10, "aimm": 0, "architectur": 12, "argument": 10, "backend": 7, "call": 6, "cancel": 8, "cite": 0, "client": 1, "common": 12, "compon": 12, "configur": 12, "content": 4, "control": 9, "creat": 8, "create_inst": 10, "data": 12, "decor": 6, "develop": 5, "engin": 11, "environ": 5, "event": [7, 8], "fit": [8, 10], "get": 3, "hat": 12, "instal": 5, "instanc": 8, "interfac": 10, "introduct": 5, "json": 10, "login": 10, "logout": 10, "multiprocess": 11, "plugin": 6, "predict": [8, 10], "preprocess": 10, "repl": [2, 10], "represent": 10, "request": 8, "rpc": 10, "server": 12, "sqlite": 7, "start": 3, "state": [8, 10, 11], "structur": 12, "updat": 8, "update_inst": 10, "usag": 12}}) \ No newline at end of file diff --git a/build/docs/server/backend.html b/build/docs/server/backend.html new file mode 100644 index 0000000..17ec0b5 --- /dev/null +++ b/build/docs/server/backend.html @@ -0,0 +1,300 @@ + + + + + + + Backend — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Backend

+

The function of backend instances is performing model persistance. They provide +functions that allow its callers to store and retrieve modules.

+

The backend configuration is one of the properties in the server configuration. +Schema does not set limits on which backend implementations are available and +instead offers an interface that allows dynamic imports of backend +implementations, requiring only a module parameter that is a Python module +name of the concrete backend implementation. The exact minimal schema is as +follows:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/backend/main.yaml#'
+type: object
+required:
+    - module
+properties:
+    module:
+        type: string
+...
+
+
+

All backend implementations need to implement the following interface:

+
+
+class aimm.server.common.Backend
+

Backend interface. In order to integrate in the aimm server, create a +module with the implementation and function create that creates a +backend instance. The function should have a signature as the +create_backend() function.

+

The event_client argument is not None if backend module also +contains function named create_subscription with the same signature as +the create_subscription(). The function receives the same backend +configuration the create function would receive and returns the +subscription object for the backend.

+
+
+abstract async get_models() List[Model]
+

Get all persisted models, requires that a deserialization function +is defined for all persisted types

+
+
Returns:
+

persisted models

+
+
+
+ +
+
+abstract async create_model(model_type: str, instance: Any)
+

Store a new model, requires that a serialization for the model type +is defined

+
+ +
+
+abstract async update_model(model: Model)
+

Replaces the old stored model with the new one, requires that a +serialization is defined for the model type

+
+ +
+
+register_model_change_cb(cb: Callable[[Model], None]) RegisterCallbackHandle
+

Register callback for backend-side model changes. Implementation +optional, defaults to ignoring the callback.

+
+ +
+
+async process_events(events: Event)
+

Implementation optional. Called when event client receives events +matched by subscription from create_backend_subscription. Ignores +events by default, with a warning log.

+
+ +
+ +
+
+aimm.server.common.create_backend(conf: Dict, event_client: Client | None = None) Backend
+

Placeholder of the backend’s create function, needs to satisfy the given +signature

+
+ +
+
+aimm.server.common.create_subscription(conf: Any) list[tuple[str, ...]]
+

Placeholder of the backends and controls optional create subscription +function, needs to satisfy the given signature

+
+ +

Only one instance of a backend can be configured on a aimm server and its +configuration depends on the concrete implementation of the interface. Two +kinds of backend implementations are available with the aimm package - sqlite +and event backend.

+
+

SQLite

+

SQLite backend stores all the models into a SQLite database. The configuration +needs to follow the schema:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/backend/sqlite.yaml#'
+type: object
+required:
+    - path
+properties:
+    path:
+        type: string
+...
+
+
+

Only configuration property is path - path to the SQLite file where data is +stored.

+
+
+

Event

+

The event backend makes use of Hat’s event infrastructure to create and access +events that contain model blobs. It requires a connection to hat event server +to be configured and its configuration needs to follow the schema:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/backend/event.yaml#'
+type: object
+required:
+    - model_prefix
+properties:
+    model_prefix:
+        type: array
+        items:
+            type: string
+...
+
+
+

The only configurable property, model_prefix is the prefix of the model +blob event’s types. The events that the model raises will have the following +structure:

+
+
    +
  • event type: [<model_prefix>, <instance_id>]

  • +
  • source timestamp: None

  • +
  • payload: dictionary that follows the schema:

    +
    ---
    +type: object
    +required:
    +    - type
    +    - instance
    +properties:
    +    type:
    +        type: string
    +        description: |
    +            model type, used to pair with deserialization function,
    +            when needed
    +    instance:
    +        type: string
    +        description: base64 encoded serialized instance
    +...
    +
    +
    +
  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/server/control/event.html b/build/docs/server/control/event.html new file mode 100644 index 0000000..9a4c42d --- /dev/null +++ b/build/docs/server/control/event.html @@ -0,0 +1,450 @@ + + + + + + + Event — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Event

+

Event control communicates with hat’s event server and, responding to events, +calls engine’s methods and reports their results over the same event bus. The +control’s configuration needs to satisfy the following schema:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/control/event.yaml#'
+type: object
+required:
+    - event_prefixes
+    - state_event_type
+    - action_state_event_type
+properties:
+    type:
+        const: event
+    event_prefixes:
+        type: object
+        properties:
+            create_instance:
+                type: array
+                items:
+                    type: string
+            add_instance:
+                type: array
+                items:
+                    type: string
+            update_instance:
+                type: array
+                items:
+                    type: string
+            fit:
+                type: array
+                items:
+                    type: string
+            predict:
+                type: array
+                items:
+                    type: string
+            cancel:
+                type: array
+                items:
+                    type: string
+    state_event_type:
+        type: array
+        items:
+            type: string
+    action_state_event_type:
+        type: array
+        items:
+            type: string
+...
+
+
+
+

State

+

Upon any engine state change, event control registers an event with the +following structure:

+
+
    +
  • type: [<state_type>]

  • +
  • source_timestamp is None

  • +
  • payload: JSON with following schema:

    +
    ---
    +type: object
    +required:
    +    - models
    +    - actions
    +properties:
    +    models:
    +        type: object
    +        patternProperties:
    +            '(.)+':
    +                type: string
    +                description: model type
    +    actions:
    +        type: object
    +        description: copied as-is from engine's state
    +...
    +
    +
    +
  • +
+
+
+
+

Action requests and states

+

Engine’s functions are used as reactions to received events. Control receives +events with types that match one of the [<prefix>, '*'] query types and +calls the functions depending on which exact prefix has matched.

+

Prior to the calling the functions, arguments passed in the payload may be +preprocessed and converted into aimm.server.common.DataAccess objects +of they have a specific structure. Arguments of any call specified in the +request event’s payload should have the following structure:

+
---
+id: '#object_arg'
+oneOf:
+  - {}
+  - description: converted to ``common.DataAccess``
+    type: object
+    required:
+        - type
+        - name
+        - args
+        - kwargs
+    properties:
+        type:
+            const: data_access
+        name:
+            type: string
+            description: data access plugin name
+        args:
+            type: array
+            items:
+                '$ref': '#object_arg'
+        kwargs:
+            patternProperties:
+                '(.)+':
+                    '$ref': '#object_arg'
+...
+
+
+

The control registers events that refer directly to the request that started +the action and that contain information on the actions state, and, if an action +returns it, its result. The event has the following structure:

+
+
    +
  • type: [<action_state_type>]

  • +
  • source_timestamp is None

  • +
  • payload: JSON with the following schema:

    +
    ---
    +type: object
    +required:
    +    - request_id
    +    - result
    +properties:
    +    request_id:
    +        type: object
    +        description: |
    +            event id that started the execution
    +    status:
    +        enum:
    +            - IN_PROGRESS
    +            - DONE
    +            - FAILED
    +            - CANCELLED
    +    result: {}
    +...
    +
    +
    +
  • +
+
+
+

Create instance

+

Incoming event structure:

+
+
    +
  • type: [<create_instance_prefix>, '*']

  • +
  • payload with structure:

    +
    ---
    +type: object
    +required:
    +    - model_type
    +    - args
    +    - kwargs
    +properties:
    +    model_type:
    +        type: string
    +    args:
    +        type: array
    +        items:
    +            type: '#object_arg'
    +    kwargs:
    +        patternProperties:
    +            '(.)+':
    +                '$ref': '#object_arg'
    +...
    +
    +
    +
  • +
+
+

Result passed in the response is either an integer, representing ID of the +generated instance, or None if creation has failed.

+
+
+

Add instance

+

Incoming event structure:

+
+
    +
  • type: [<add_instance_prefix>, '*']

  • +
  • payload with structure:

    +
    ---
    +type: object
    +required:
    +    - model_type
    +    - instance
    +properties:
    +    model_type:
    +        type: string
    +    instance:
    +        type: string
    +        description: base64 encoded instance
    +...
    +
    +
    +
  • +
+
+

Result passed in the response is either an integer, representing ID of the +generated instance, or None if creation has failed.

+
+
+

Update instance

+

Incoming event structure:

+
+
    +
  • type: [<update_instance_prefix>, <instance_id>, '*']

  • +
  • payload with structure:

    +
    ---
    +type: object
    +required:
    +    - model_type
    +    - instance
    +properties:
    +    model_type:
    +        type: string
    +    instance:
    +        type: string
    +        description: base64 encoded instance
    +...
    +
    +
    +
  • +
+
+

Result is a boolean, true if update was performed successfully.

+
+
+

Fit

+

Incoming event structure:

+
+
    +
  • type: [<fit_prefix>, <instance_id>, '*']

  • +
  • payload with structure:

    +
    ---
    +type: object
    +required:
    +    - args
    +    - kwargs
    +properties:
    +    args:
    +        type: array
    +        items:
    +            type:
    +                '$ref': '#object_arg'
    +    kwargs:
    +        patternProperties:
    +            '(.)+':
    +                '$ref': '#object_arg'
    +...
    +
    +
    +
  • +
+
+

Result is a boolean, true if update was performed successfully.

+
+
+

Predict

+

Incoming event structure:

+
+
    +
  • type: [<predixt_prefix>, <instance_id>, '*']

  • +
  • payload with structure:

    +
    ---
    +type: object
    +required:
    +    - args
    +    - kwargs
    +properties:
    +    args:
    +        type: array
    +        items:
    +            type:
    +                '$ref': '#object_arg'
    +    kwargs:
    +        patternProperties:
    +            '(.)+':
    +                '$ref': '#object_arg'
    +...
    +
    +
    +
  • +
+
+

Result is the prediction, exact value returned by the call to the predict +plugin.

+
+
+

Cancel

+

Any action that can have the state IN_PROGRESS may be canceled. This is +done by registering a cancel event, with the following structure:

+
+
    +
  • type [<cancel_prefix>, '*']

  • +
  • JSON payload that is a dictionary representation of the id of the event +that started the action

  • +
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/server/control/index.html b/build/docs/server/control/index.html new file mode 100644 index 0000000..d8c537c --- /dev/null +++ b/build/docs/server/control/index.html @@ -0,0 +1,204 @@ + + + + + + + Control — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Control

+

Control interface is used to communicate to external actors and call engine’s +functions. They may notify their respective actors of state changes and serve +as a general entry point for any external client.

+

Control configuration is one of the properties in the server configuration. +Similar to backend, control interface works on the principle of dynamic +imports, configuration schema only consisting of a list of objects (multiple +controls may run in parallel) that require a module parameter, which is a +Python module name that contains the concrete control implementation. The exact +configuration schema looks as follows:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/control/main.yaml#'
+type: array
+items:
+    type: object
+    required:
+        - module
+    properties:
+        module:
+            type: string
+...
+
+
+

All control implementations should implement the following interface:

+
+
+class aimm.server.common.Control
+

Control interface. In order to integrate in the aimm server, create a +module with the implementation and function create that creates a +control instance and should have a signature as the create_control() +function.

+

The event_client argument is not None if control module also +contains function named create_subscription with the same signature as +the create_subscription(). The function receives the same control +configuration the create function would receive and returns the list of +subscriptions ProxyClient should subscribe to.

+
+
+async process_events(events: Collection[Event])
+

Implementation optional. Called when event client receives events +matched by subscription from create_backend_subscription. Ignores +events by default, with a warning log.

+
+ +
+ +
+
+aimm.server.common.create_control(conf: Dict, engine: Engine, event_client: Client | None = None) Control
+

Placeholder of the control’s create function, needs to satisfy the given +signature

+
+ +
+
+aimm.server.common.create_subscription(conf: Any) list[tuple[str, ...]]
+

Placeholder of the backends and controls optional create subscription +function, needs to satisfy the given signature

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/server/control/repl.html b/build/docs/server/control/repl.html new file mode 100644 index 0000000..cb59ead --- /dev/null +++ b/build/docs/server/control/repl.html @@ -0,0 +1,433 @@ + + + + + + + REPL — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

REPL

+

REPL control serves as an interface to the REPL client. Its configuration has +the following schema:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/control/repl.yaml#'
+type: object
+required:
+    - server
+    - users
+properties:
+    type:
+        const: repl
+    server:
+        type: object
+        required:
+            - host
+            - port
+        properties:
+            host:
+                type: string
+            port:
+                type: integer
+    users:
+        type: array
+        items:
+            type: object
+            required:
+                - username
+                - password
+            properties:
+                username:
+                    type: string
+                password:
+                    type: string
+...
+
+
+

REPL control works as a Websocket server, using the hat-juggler protocol. It +translates engine’s state into JSON serializable data and transfers it to its +clients over local-remote data synchronization. It also provides its clients an +RPC interface, which allows them to send messages causing calls to engine’s +functions and receive their results.

+

The server listens at the address ws://<host>:<port>/ws, where host and +port are configuration parameters. After connecting, the clients have +access to the initial state and RPC actions.

+
+

State

+

Before accessing the state, clients need to be authorized (described in the +Actions section). The initial state, before login is just an empty JSON object. +After successful login, engine’s state is translated into JSON with the +following schema:

+
---
+type: object
+required:
+    - actions
+    - models
+properties:
+    actions:
+        type: object
+        patternProperties:
+            "(.)+":
+                type: object
+                description: copied directly from engine's state property
+    models:
+        type: object
+        patternProperties:
+            "(.)+":
+                type: object
+                required:
+                    - type
+                    - id
+                    - instance
+                properties:
+                    type:
+                        type: string
+                    id:
+                        type: integer
+                    instance:
+                        type: string
+                        description: base64 encoded instance
+...
+
+
+
+
+

RPC interface

+

RPC interface provides several actions its clients may call. These actions are +documented in this section, along with some additional considerations about +result JSON representations and advanced argument passing.

+
+

Actions

+
+

login

+

Authorizes the user with given username and password. Successful authorization +gives access to other actions and full state.

+
+
Arguments:
    +
  • username (str)

  • +
  • password (str)

  • +
+
+
+
+

Note

+

Login procedure here is added pro forma and might not provide optimal +level of security for some use cases. If this is the case, it is advised to +develop a separate control that implements a more appropriate procedure.

+
+
+
+

logout

+

Logs the user out.

+
+
+

create_instance

+

Connects to engine’s create_instance method. Argument preprocessing is +supported.

+
+
Arguments:
    +
  • model_type (str): model type as defined in plugins

  • +
  • args (List[Any]): positional arguments passed to the plugin method

  • +
  • kwargs (Dict[str: Any]): keyword arguments passed to the plugin +method

  • +
+
+
+

Returns JSON representation of the model.

+
+
+

add_instance

+

Connects to engine’s add_instance method.

+
+
Arguments:
    +
  • model_type (str): model type as defined in plugins

  • +
  • instance (str): base64 encoded serialized model instance

  • +
+
+
+

Returns JSON representation of the model.

+
+
+

update_instance

+

Connects to engine’s update_instance method.

+
+
Arguments:
    +
  • model_type (str): model type as defined in plugins

  • +
  • instance_id (int): ID of the instance that is being updated

  • +
  • instance (str): base64 encoded serialized model instance

  • +
+
+
+

Returns JSON representation of the model.

+
+
+

fit

+

Connects to engine’s fit method. Argument preprocessing is supported.

+
+
Arguments:
    +
  • instance_id (int): ID of the instance that is being fitted

  • +
  • args (List[Any]): positional arguments for the fitting method

  • +
  • kwargs (Dict[str, Any]): keyword arguments for the fitting method

  • +
+
+
+

Returns JSON representation of the model.

+
+
+

predict

+

Connects to engine’s predict method. Argument preprocessing is supported.

+
+
Arguments:
    +
  • instance_id (int): ID of the instance that is being fitted

  • +
  • args (List[Any]): positional arguments for the fitting method

  • +
  • kwargs (Dict[str, Any]): keyword arguments for the fitting method

  • +
+
+
+

Returns prediction converted to JSON.

+
+
+
+

JSON representations

+

Some data structures mentioned in the sections above are, by the default, not +JSON serializable and JSON schema of the structure they take is described in +this section.

+

Models schema:

+
---
+type: object
+required:
+    - instance_id
+    - model_type
+    - instance
+properties:
+    instance_id:
+        type: integer
+    model_type:
+        type: string
+    instance:
+        type: string
+        description: base64 encoded serialized model instance
+...
+
+
+

Prediction schema:

+
---
+    oneOf:
+      - {}
+      - type: object
+        required:
+            - type
+            - data
+        properties:
+            type:
+                enum:
+                    - numpy_array
+                    - pandas_dataframe
+                    - pandas_series
+            data:
+                type: object
+                description: |
+                    prediction result serialized as json, for numpy
+                    arrays and pandas Series, tolist methods are used
+                    and for dataframe, its to_dict method is used
+...
+
+
+
+
+

Argument preprocessing

+

Actions correspond to engine’s interface, and that interface provides support +for passing aimm.server.common.DataAccess objects, to signify that an +engine action needs to execute a data access plugin before calling the main +action. Control allows sections of messages that contain arguments to take a +specific structure that signify that that argument needs to be converted into +an object. Conversion to aimm.server.common.DataAccess, +numpy.array and pandas.DataFrame is currently supported. The +structure:

+
---
+id: '#object_arg'
+oneOf:
+  - {}
+  - description: converts to common.DataAccess
+    required:
+        - name
+        - args
+        - kwargs
+    properties:
+        type:
+            const: data_access
+        name:
+            type: string
+            description: data access plugin name
+        args:
+            type: array
+            items:
+                '$ref': '#object_arg'
+        kwargs:
+            patternProperties:
+                '(.)+':
+                    '$ref': '#object_arg'
+  - description: converts to numpy.array
+    required:
+        - data
+        - dtype
+    properties:
+        type:
+            const: numpy_array
+        data:
+            type: array
+        dtype:
+            type: string
+  - description: converts to pandas.DataFrame
+    required:
+        - data
+    properties:
+        type:
+            const: pandas_dataframe
+        data:
+            type: object
+            descirption: result of pandas.DataFrame.to_dict function
+  - description: converts to pandas.Series
+    required:
+        - data
+    properties:
+        type:
+            const: pandas_series
+        data:
+            type: object
+            descirption: result of pandas.Series.tolist function
+...
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/server/engine.html b/build/docs/server/engine.html new file mode 100644 index 0000000..2ed8e54 --- /dev/null +++ b/build/docs/server/engine.html @@ -0,0 +1,492 @@ + + + + + + + Engine — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Engine

+

Engine is the central component of the AIMM system. Its purpose is handling +manageable calls to plugins, using the backend when persistence is required and +serving its state to all controllers.

+

Engine’s part of the configuration needs to satisfy the following schema:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/engine.yaml#'
+type: object
+required:
+    - sigterm_timeout
+    - max_children
+    - check_children_period
+properties:
+    sigterm_timeout:
+        type: number
+    max_children:
+        type: number
+    check_children_period:
+        type: number
+...
+
+
+

When engine is started, it queries its backend to access all existing model +instances. The instances are then stored in engine’s state. Any component +holding a reference to the engine may use it to perform different actions, such +as creating model instances, fitting them or using them for predictions. When a +new model instance is created, or an old one is updated, state change is +notified to any components that have subscribed to state changes. Additionally, +calls to the plugins themselves are manageable and engine keeps theirs states +in its state as well.

+

Workflow actions such as fitting or using models are ran asynchronously in +separate asyncio tasks. Additionally, any plugin they call is ran in a separate +process, wrapped in a handler that allows subscriptions to call’s state (if the +plugin call supports state notification). The calls can also be cancelled, +which is done using signals - initially SIGTERM and later SIGKILL if a +configured timeout expires. Also, to avoid fork bombs, a separate pseudo-pool +is implemented to serve as an interface for process creation, disallowing +creation of new processes if a certain number of subprocesses is already +running.

+

Engine configuration reflects the multiprocessing nature of the implementation, +since all of the options refer to different timeouts and limitations:

+
+
    +
  • sigterm_timeout is the number of seconds waited after SIGTERM was sent +to a process before SIGKILL is sent if it doesn’t terminate

  • +
  • max_children is the maximum amount of children - concurrent subprocesses +that can run at the same time

  • +
  • check_children_period - the check for children counts is done +periodically and this setting indicates how often it is checked

  • +
+
+

Engine module provides the following interface:

+
+
+class aimm.server.common.Engine
+

Engine interface

+
+
+abstract property state: Dict
+

Engine state, contains references to all models and actions. It’s +never modified in-place, instead subscribe_to_state_change() +should be used

+
+ +
+
+abstract subscribe_to_state_change(cb: Callable[[], None]) RegisterCallbackHandle
+

Subscribes to any changes to the engine state

+
+ +
+
+abstract create_instance(model_type: str, *args: Any, **kwargs: Any) Action
+

Starts an action that creates a model instance and stores it in +state.

+
+
Parameters:
+
    +
  • model_type – model type

  • +
  • *args – instantiation arguments

  • +
  • **kwargs – instantiation keyword arguments

  • +
+
+
+
+ +
+
+abstract async add_instance(model_type: str, instance: Any) Model
+

Adds existing instance to the state

+
+ +
+
+abstract async update_instance(model: Model)
+

Update existing instance in the state

+
+ +
+
+abstract fit(instance_id: int, *args: Any, **kwargs: Any) Action
+

Starts an action that fits an existing model instance. The used +fitting function is the one assigned to the model type. The instance, +while it is being fitted, is not accessible by any of the other +functions that would use it (other calls to fit, predictions, etc.).

+
+
Parameters:
+
    +
  • instance_id – id of model instance that will be fitted

  • +
  • *args – arguments to pass to the fitting function - if of type +aimm.server.common.DataAccess, the value passed to the +fitting function is the result of the call to that plugin, +other arguments are passed directly

  • +
  • **kwargs – keyword arguments, work the same as the positional +arguments

  • +
+
+
+
+ +
+
+abstract predict(instance_id: int, *args: Any, **kwargs: Any) Action
+

Starts an action that uses an existing model instance to perform a +prediction. The used prediction function is the one assigned to model’s +type. The instance, while prediction is called, is not accessible by +any of the other functions that would use it (other calls to predict, +fittings, etc.). If instance has changed while predicting, it is +updated in the state and database.

+
+
Parameters:
+
    +
  • instance_id – id of the model instance used for prediction

  • +
  • *args – arguments to pass to the predict function - if of type +aimm.server.common.DataAccess, the value passed to the +predict function is the result of the call to that plugin, +other arguments are passed directly

  • +
  • **kwargs – keyword arguments, work the same as the positional +arguments

  • +
+
+
Returns:
+

Reference to task of the manageable predict call, result of it is +the model’s prediction

+
+
+
+ +
+ +
+
+class aimm.server.common.Action
+

Represents a manageable call. Is an aio.Resource so call can be +cancelled using async_close.

+
+
+abstract async wait_result() Any
+

Wait until call returns a result. May raise +asyncio.CancelledError in case the call was cancelled.

+
+ +
+ +

Since there are no strict limitations on the arguments that may be passed to +plugins, i.e., positional and keyword arguments are mostly passed as-is, +callers of the actions have the options of passing different special kinds of +objects as arguments. These objects are interpreted by the engine as subactions +that need to be executed before the main action. E.g., a fitting function may +expect a dataset as an input, and while it is possible to pass the dataset +directly to engine’s Engine.fit() call, the caller could create a +aimm.server.common.DataAccess object and pass it instead. This would +indicate to the engine that it needs to use the data access plugin to access +the required data before fitting. All subactions are also ran in a separate +subprocesses and notify their progress through state.

+
+

State

+

State is a dictionary consisting of two properties, models and actions. +Models are a dictionary with instance IDs as keys and +aimm.server.common.Model instances as values. Actions are also a +dictionary, with the following structure:

+
---
+description: keys are action IDs
+patternProperties:
+    '(.)+':
+        oneOf:
+          - type: 'null'
+            description: prior to start of the action call
+          - type: object
+            required:
+                - meta
+                - progress
+            properties:
+                meta:
+                    type: object
+                    required:
+                        - call
+                    properties:
+                        call:
+                            type: string
+                            description: call that the action is making
+                        model_type:
+                            type: string
+                        model:
+                            type: integer
+                        args:
+                            type: array
+                        kwargs:
+                            type: object
+                progress:
+                    enum:
+                        - accessing_data
+                        - executing
+                        - complete
+                data_access:
+                    type: object
+                    description: |
+                        keys represent argument IDs (numbers for
+                        positional, strings for named), values are set by
+                        plugin's state callbacks
+                action:
+                    description: set by plugin state callback
+...
+
+
+
+
+

Multiprocessing

+

The details of the multiprocessing implementation are placed in a separate +module, aimm.server.mprocess. This module is in charge of providing an +interface for managed process calls. The central class of the module is the +aimm.server.mprocess.ProcessManager. Its purpose is similar to one of +a standard multiprocessing.Pool, main difference being that it does +not keep an exact amount of process workers alive at all times and instead +holds an asyncio.Condition that prevents creation of new processes +until the number of children is under the max_children configuration +parameter.

+

The manager is implemented in the following class:

+
+
+class aimm.server.mprocess.ProcessManager(max_children: int, async_group: Group, check_children_period: float, sigterm_timeout: float)
+

Class used to create ProcessHandler objects and limit the +amount of concurrently active child processes.

+
+
Parameters:
+
    +
  • max_children – maximum number of child processes that may be created

  • +
  • async_group – async group

  • +
  • check_children_period – number of seconds waited before checking if a +child process may be created and notifying pending handlers

  • +
  • sigterm_timeout – number of seconds waited before sending SIGKILL if a +child process does not terminate after SIGTERM

  • +
+
+
+
+
+property async_group: Group
+

Group controlling resource’s lifetime.

+
+ +
+
+create_handler(state_cb: Callable[[Any], None]) ProcessHandler
+

Creates a ProcessHandler

+
+
Parameters:
+

state_cb (Callable[List[Any], None]) – state callback for changes +in the process call

+
+
Returns:
+

ProcessHandler

+
+
+
+ +
+ +

The process calls are wrapped in a aimm.server.mprocess.ProcessHandler +instance, whose interface allows callers to terminate the process call. It also +allows callers to pass their state change callback functions which are called +whenever the process’ state changes.

+

After calling aimm.server.mprocess.ProcessManager.create_handler() and +receiving a process handler, the call can be made using the +aimm.server.mprocess.ProcessHandler.run() function, which, in reality, +first spawns an asyncio task that blocks until the process manager allows +creation of a new process and only then actually creates a new process.

+

The state notification is done using callbacks and multiprocessing pipes. +Process handler receives a state_cb argument in its constructor and this is +the function used to notify states to the rest of the system. It also provides +a method proc_notify_state_change, which is a callback passed to the +function running in the separate process. This function uses a +multiprocessing.Pipe object to send function’s state values (need to +be pickle-able). Handlers also have internal state listening loops, running in +the main asyncio event loop, that react to receiving these state changes and +notify the rest of the system using the state_cb passed in the constructor. +Result of the separated process call is also passed through a separate pipe and +set as the result of the aimm.server.mprocess.ProcessHandler.result +property.

+

The complete class docstring:

+
+
+class aimm.server.mprocess.ProcessHandler(async_group: Group, sigterm_timeout: float, state_cb: Callable[[Any], None], condition: Condition)
+

Handler for calls in separate processes. Created through +ProcessManager.create().

+
+
Parameters:
+
    +
  • async_group (hat.aio.Group) – async group

  • +
  • sigterm_timeout (float) – time waited until process handles SIGTERM +before sending SIGKILL during forced shutdown

  • +
  • state_cb (Optional[Callable[Any]]) – state change cb

  • +
  • condition (asyncio.Condition) – condition that notifies when a new +process may be created

  • +
+
+
+
+
+property async_group: Group
+

Group controlling resource’s lifetime.

+
+ +
+
+proc_notify_state_change(state: Any)
+

To be passed to and ran in the separate process call. Notifies the +handler of state change, new state is passed to state_cb received +in the constructor.

+
+
Parameters:
+

state – call state, needs to be pickleable

+
+
+
+ +
+
+async run(fn: Callable, *args: Any, **kwargs: Any)
+

Requests the start of function execution in the separate process.

+
+
Parameters:
+
    +
  • fn – function that will be called

  • +
  • *args – positional arguments, need to be pickleable

  • +
  • **kwargs – keyword arguments, need to be pickleable

  • +
+
+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/docs/server/index.html b/build/docs/server/index.html new file mode 100644 index 0000000..04a79cc --- /dev/null +++ b/build/docs/server/index.html @@ -0,0 +1,325 @@ + + + + + + + Server — aimm documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Server

+

AIMM server is a server application that listens on one or several interfaces +(web services, multiprocess communication, etc.) and allows the clients that +connect to them to perform various operations with its configured plugins.

+
+

Usage

+

After successful installation, the server can be run by calling +aimm-server in the command line. The command has the following interface:

+
+
usage: aimm-server [-h] [--conf path]
+
+Run AIMM server
+
+options:
+  -h, --help   show this help message and exit
+  --conf path  configuration defined by aimm://server/main.yaml# (default
+               $XDG_CONFIG_HOME/aimm/server.yaml)
+
+
+
+
+
+

Configuration

+

To use the server, it needs to be configured. The configuration is a YAML file +that contains options specific to different components of which the system +consists. It also contains options for logging and Hat configuration. It needs +to validate against the following schema:

+
---
+$schema: 'http://json-schema.org/schema#'
+id: 'aimm://server/main.yaml#'
+type: object
+required:
+    - name
+    - engine
+    - backend
+    - control
+    - plugins
+    - log
+properties:
+    name:
+        type: string
+    engine:
+        '$ref': 'aimm://server/engine.yaml#'
+    backend:
+        '$ref': 'aimm://server/backend/main.yaml#'
+    control:
+        '$ref': 'aimm://server/control/main.yaml#'
+    plugins:
+        '$ref': 'aimm://plugins.yaml#'
+    hat:
+        '$ref': 'aimm://server/hat.yaml#'
+    log:
+        type: object
+...
+
+
+

Logging configuration is mandatory and its values are passed directly to the +logging.basicConfig() function. Remaining properties of the root part +of the configuration directly correlate to the main components of the server’s +architecture, are more complex and will be explained separately.

+
+
+

Hat

+

AIMM server configuration contains options that allow it to work as a part of +Hat infrastructure. The optional hat property of the configuration +contains the options that allow this integration.

+

Connection to monitor is the minimal requirement in order to integrate to the +Hat network and the configuration parameters are as specified by the monitor +documentation. Additionally, if event_server_group field is set, AIMM +server will also attempt to connect to event server with in the given group. +If these properties are configured but the server cannot connect to Hat +components, it will halt and wait until it manages to connect.

+

Since AIMM server supports dynamic imports of different backend and control +implementations that may use the event server connection to receive and +register events, the modules containing these components need to implement a +get_subscriptions function that returns a list of event server +subscriptions. It should have the following type:

+
+
+aimm.server.common.CreateSubscription
+

Type of the create_subscription function that the dynamically imported +controls and backends may implement. Receives component configuration as the +only argument and returns a subscription object.

+

alias of Callable[[Dict], Subscription]

+
+ +

A combined list of all these subscriptions is then used as the global +subscription of the entire server. If the get_subscriptions function is +provided, the component implementation will receive an instance of a +hat.event.eventer.Client, as specified here.

+
+
+

Architecture

+

After initialization and, optionally, Hat integration, the architecture of AIMM +server may be represented with the following diagram:

+../_images/server.svg +

The main components are:

+
+
    +
  • engine - implements function calls that connect control interfaces with +plugins, keeps the state of the application and uses backend to store it +when neccessary

  • +
  • backend - handles state data persistence

  • +
  • plugins - contain model and data access implementations

  • +
  • control - provides external interfaces for clients, calls engine functions +and notifies clients of state changes or call results

  • +
+
+
+
+

Common data structures

+

The server has some common data structures used in different components, +required as arguments at some methods or returned as their results. They are +documented in this section.

+
+
+class aimm.server.common.Model(instance: Any, model_type: str, instance_id: int)
+

Server’s representation of objects returned by +plugins.exec_instantiate(). Contains all metadata necessary to +identify and perform other actions with it.

+
+
+instance: Any
+

instance

+
+ +
+
+model_type: str
+

model type, used to identify which plugin to use

+
+ +
+
+instance_id: int
+

instance id

+
+ +
+ +
+
+class aimm.server.common.DataAccess(name: str, args: Iterable, kwargs: Dict[str, Any])
+

Representation of a plugins.exec_data_access() call. May be passed +as an argument in some methods, indicating that data needs to be retrieved +prior to calling the main action. See more details on the exact method +docstrings.

+
+
+name: str
+

name of the data access type, used to identify which plugin to use

+
+ +
+
+args: Iterable
+

positional arguments to be passed to the plugin call

+
+ +
+
+kwargs: Dict[str, Any]
+

keyword arguments to be passed to the plugin call

+
+ +
+ +
+
+

Components

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/cite.rst b/docs/cite.rst new file mode 100644 index 0000000..e4cfbcf --- /dev/null +++ b/docs/cite.rst @@ -0,0 +1,19 @@ +Citing AIMM +=========== + +If you reference AIMM in your research please consider citing the paper that +specifies its architecture and interfaces. The paper is available `here +`_. You can also use the +following BiBTeX entry: + +.. code-block:: bibtex + + @ARTICLE{9734070, + author={Sičanica, Zlatan and Sučić, Stjepan and Milašinović, Boris}, + journal={IEEE Access}, + title={Architecture of an Artificial Intelligence Model Manager for Event-Driven Component-Based SCADA Systems}, + year={2022}, + volume={10}, + number={}, + pages={30414-30426}, + doi={10.1109/ACCESS.2022.3159715}} diff --git a/docs/clients/index.rst b/docs/clients/index.rst new file mode 100644 index 0000000..d4200b7 --- /dev/null +++ b/docs/clients/index.rst @@ -0,0 +1,11 @@ +Clients +======= + +The aimm package provides applications and libraries that can be used to +communicate with some of the initially available :doc:`server controls +`. These are referred to as clients and the purpose of this +chapter provides their documentations. + +.. toctree:: + + repl diff --git a/docs/clients/repl.rst b/docs/clients/repl.rst new file mode 100644 index 0000000..19e0dda --- /dev/null +++ b/docs/clients/repl.rst @@ -0,0 +1,12 @@ +REPL +==== + +REPL client is a Python library that communicates with the :doc:`REPL control +<../../server/control/repl>` and provides an interface that triggers +interactions with it. Its intended purpose is to be imported and used in REPL +environment, but there are no real restrictions on it being used as a library. +While client implementation is contained within the ``aimm.client.repl`` +module, which is specified hereafter. + +.. automodule:: aimm.client.repl + :members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..fb857b2 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,19 @@ +import importlib.metadata + +project = "aimm" +copyright = "2024, Zlatan Sičanica" +author = "Zlatan Sičanica" +version = "1.2.dev0" + +extensions = [ + "sphinxcontrib.programoutput", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx_rtd_theme", +] + +html_theme = "sphinx_rtd_theme" + +html_static_path = [] + +autodoc_member_order = "bysource" diff --git a/docs/getting_started.rst b/docs/getting_started.rst new file mode 100644 index 0000000..d744323 --- /dev/null +++ b/docs/getting_started.rst @@ -0,0 +1,52 @@ +Getting started +=============== + +After :doc:`installing AIMM `, its CLI and libraries become +available. Since the main purpose of the server is providing an API to +dynamically defined plugins, the first step is defining the plugins the server +will provide an interface for. The following code shows an example of a module +containing multiple plugin definitions: + +.. literalinclude:: ../examples/0001/plugins/sklearn_wrapper.py + +The plugins defined are: + + * data access for Iris dataset inputs and outputs + * model implementation that is actually a wrapper around sklearn's SVC + +For more information on the plugin interface, consult the :doc:`documentation +entry `. + +The next step is server configuration. This involves writing a YAML file +containing the necessary settings. An example of such a configuration, +following up to the previous example, may be: + +.. literalinclude:: ../examples/0001/aimm.yaml + :language: yaml + +For more details on configuring and running the server, consult the +:doc:`documentation entry `. + +Running the server now starts a service that listens on the address +``127.0.0.1:9999`` and allows clients to connect, create, fit and use SVC +models. Since the only configured control is REPL control, it should be +possible to connect using the :doc:`REPL client `. Following +snippet shows how the library may be used: + +.. code-block:: python + + from aimm.client import repl + + async def run(): + aimm = repl.AIMM() + await aimm.connect('ws://127.0.0.1:9999/ws') + m = await aimm.create_instance('plugins.sklearn.SVC') + + await m.fit(repl.DataAccessArg('iris_inputs'), repl.DataAccessArg('iris_outputs')) + await m.predict(repl.DataAccessArg('iris_inputs')) + +The code would connect to the server, create an SVC instance, remotely fit it +with Iris data and use it to perform classification. + +Example of Hat integration is available in the `examples directory +`_. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..fcbd126 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,12 @@ +Content +------- + +.. toctree:: + :maxdepth: 1 + + introduction + getting_started + plugins + server/index + clients/index + cite diff --git a/docs/introduction.rst b/docs/introduction.rst new file mode 100644 index 0000000..f473ca8 --- /dev/null +++ b/docs/introduction.rst @@ -0,0 +1,5 @@ +Introduction +============ + +.. include:: ../README.rst + :start-line: 2 diff --git a/docs/plugins.rst b/docs/plugins.rst new file mode 100644 index 0000000..189ef9a --- /dev/null +++ b/docs/plugins.rst @@ -0,0 +1,64 @@ +Plugins +======= + +AIMM system relies on user-written plugins for implementations of machine +learning models, preprocessing and data-fetching methods. Both server and its +clients may use the plugin API. The plugins are implemented as Python modules +that are dynamically imported at some point during servers or clients runtime. +Within these modules, plugin implementators should use the decorators from the +``aimm.plugins`` module to indicate which functions and classes are entry +points to various machine learning workflow actions. They may also, through +decorator arguments, pass information to plugin callers, allowing them to +further declare semantics of some of their arguments (e.g. declare that an +argument should receive a callback function for progress notification). + +Before accessing any plugins, they need to be imported and initialized. One way +to do this (other then importing them manually) is by calling the +initialization function: + +.. autofunction:: aimm.plugins.initialize + +Initialize configuration schema: + +.. literalinclude:: ../schemas_json/plugins.yaml + :language: yaml + + +.. warning:: The AIMM plugin interface does not create a sandbox environment + when executing plugins, it is up to the implementator to make sure not to + perform unsafe actions + +Decorators +---------- + +.. autodecorator:: aimm.plugins.data_access +.. autodecorator:: aimm.plugins.instantiate +.. autodecorator:: aimm.plugins.fit +.. autodecorator:: aimm.plugins.predict +.. autodecorator:: aimm.plugins.serialize +.. autodecorator:: aimm.plugins.deserialize + +It is common for a model type to have all of the above defined functions, for +this reason the following class and decorator are introduced: + +.. autoclass:: aimm.plugins.Model + :members: +.. autofunction:: aimm.plugins.model + + +Calling plugins +--------------- + +After implementing and loading plugins, they can be called using following +functions: + +.. autofunction:: aimm.plugins.exec_data_access +.. autofunction:: aimm.plugins.exec_instantiate +.. autofunction:: aimm.plugins.exec_fit +.. autofunction:: aimm.plugins.exec_predict +.. autofunction:: aimm.plugins.exec_serialize +.. autofunction:: aimm.plugins.exec_deserialize + +State callbacks have the following signature: + +.. autodata:: aimm.plugins.StateCallback diff --git a/docs/server/backend.rst b/docs/server/backend.rst new file mode 100644 index 0000000..07b704f --- /dev/null +++ b/docs/server/backend.rst @@ -0,0 +1,75 @@ +Backend +======= + +The function of backend instances is performing model persistance. They provide +functions that allow its callers to store and retrieve modules. + +The backend configuration is one of the properties in the server configuration. +Schema does not set limits on which backend implementations are available and +instead offers an interface that allows dynamic imports of backend +implementations, requiring only a ``module`` parameter that is a Python module +name of the concrete backend implementation. The exact minimal schema is as +follows: + +.. literalinclude:: ../../schemas_json/server/backend/main.yaml + :language: yaml + +All backend implementations need to implement the following interface: + +.. autoclass:: aimm.server.common.Backend + :members: +.. autofunction:: aimm.server.common.create_backend +.. autofunction:: aimm.server.common.create_subscription + +Only one instance of a backend can be configured on a aimm server and its +configuration depends on the concrete implementation of the interface. Two +kinds of backend implementations are available with the aimm package - sqlite +and event backend. + +SQLite +------ + +SQLite backend stores all the models into a SQLite database. The configuration +needs to follow the schema: + +.. literalinclude:: ../../schemas_json/server/backend/sqlite.yaml + :language: yaml + +Only configuration property is ``path`` - path to the SQLite file where data is +stored. + +Event +----- + +The event backend makes use of Hat's event infrastructure to create and access +events that contain model blobs. It requires a connection to hat event server +to be configured and its configuration needs to follow the schema: + +.. literalinclude:: ../../schemas_json/server/backend/event.yaml + :language: yaml + +The only configurable property, ``model_prefix`` is the prefix of the model +blob event's types. The events that the model raises will have the following +structure: + + * event type: ``[, ]`` + * source timestamp: None + * payload: dictionary that follows the schema: + + .. code-block:: yaml + + --- + type: object + required: + - type + - instance + properties: + type: + type: string + description: | + model type, used to pair with deserialization function, + when needed + instance: + type: string + description: base64 encoded serialized instance + ... diff --git a/docs/server/control/event.rst b/docs/server/control/event.rst new file mode 100644 index 0000000..3a12a24 --- /dev/null +++ b/docs/server/control/event.rst @@ -0,0 +1,261 @@ +Event +===== + +Event control communicates with hat's event server and, responding to events, +calls engine's methods and reports their results over the same event bus. The +control's configuration needs to satisfy the following schema: + +.. literalinclude:: ../../../schemas_json/server/control/event.yaml + :language: yaml + +State +----- + +Upon any engine state change, event control registers an event with the +following structure: + + * type: ``[]`` + * source_timestamp is None + * payload: JSON with following schema: + + .. code-block:: yaml + + --- + type: object + required: + - models + - actions + properties: + models: + type: object + patternProperties: + '(.)+': + type: string + description: model type + actions: + type: object + description: copied as-is from engine's state + ... + +Action requests and states +-------------------------- + +Engine's functions are used as reactions to received events. Control receives +events with types that match one of the ``[, '*']`` query types and +calls the functions depending on which exact prefix has matched. + +Prior to the calling the functions, arguments passed in the payload may be +preprocessed and converted into :class:`aimm.server.common.DataAccess` objects +of they have a specific structure. Arguments of any call specified in the +request event's payload should have the following structure: + +.. code-block:: yaml + + --- + id: '#object_arg' + oneOf: + - {} + - description: converted to ``common.DataAccess`` + type: object + required: + - type + - name + - args + - kwargs + properties: + type: + const: data_access + name: + type: string + description: data access plugin name + args: + type: array + items: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +The control registers events that refer directly to the request that started +the action and that contain information on the actions state, and, if an action +returns it, its result. The event has the following structure: + + * type: ``[]`` + * source_timestamp is None + * payload: JSON with the following schema: + + .. code-block:: yaml + + --- + type: object + required: + - request_id + - result + properties: + request_id: + type: object + description: | + event id that started the execution + status: + enum: + - IN_PROGRESS + - DONE + - FAILED + - CANCELLED + result: {} + ... + +Create instance +''''''''''''''' + +Incoming event structure: + + * type: ``[, '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - model_type + - args + - kwargs + properties: + model_type: + type: string + args: + type: array + items: + type: '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +Result passed in the response is either an integer, representing ID of the +generated instance, or ``None`` if creation has failed. + + +Add instance +'''''''''''' + +Incoming event structure: + + * type: ``[, '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - model_type + - instance + properties: + model_type: + type: string + instance: + type: string + description: base64 encoded instance + ... + +Result passed in the response is either an integer, representing ID of the +generated instance, or ``None`` if creation has failed. + +Update instance +''''''''''''''' + +Incoming event structure: + + * type: ``[, , '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - model_type + - instance + properties: + model_type: + type: string + instance: + type: string + description: base64 encoded instance + ... + +Result is a boolean, ``true`` if update was performed successfully. + +Fit +''' + +Incoming event structure: + + * type: ``[, , '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - args + - kwargs + properties: + args: + type: array + items: + type: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +Result is a boolean, ``true`` if update was performed successfully. + +Predict +''''''' + +Incoming event structure: + + * type: ``[, , '*']`` + * payload with structure: + + .. code-block:: yaml + + --- + type: object + required: + - args + - kwargs + properties: + args: + type: array + items: + type: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + ... + +Result is the prediction, exact value returned by the call to the predict +plugin. + +Cancel +'''''' + +Any action that can have the state ``IN_PROGRESS`` may be canceled. This is +done by registering a cancel event, with the following structure: + + * type ``[, '*']`` + * JSON payload that is a dictionary representation of the id of the event + that started the action diff --git a/docs/server/control/index.rst b/docs/server/control/index.rst new file mode 100644 index 0000000..4ef5dcc --- /dev/null +++ b/docs/server/control/index.rst @@ -0,0 +1,30 @@ +Control +======= + +Control interface is used to communicate to external actors and call engine's +functions. They may notify their respective actors of state changes and serve +as a general entry point for any external client. + +Control configuration is one of the properties in the server configuration. +Similar to backend, control interface works on the principle of dynamic +imports, configuration schema only consisting of a list of objects (multiple +controls may run in parallel) that require a ``module`` parameter, which is a +Python module name that contains the concrete control implementation. The exact +configuration schema looks as follows: + +.. literalinclude:: ../../../schemas_json/server/control/main.yaml + :language: yaml + +All control implementations should implement the following interface: + +.. autoclass:: aimm.server.common.Control + :members: +.. autofunction:: aimm.server.common.create_control +.. autofunction:: aimm.server.common.create_subscription + :no-index: + +.. toctree:: + :maxdepth: 1 + + repl + event diff --git a/docs/server/control/repl.rst b/docs/server/control/repl.rst new file mode 100644 index 0000000..de4785d --- /dev/null +++ b/docs/server/control/repl.rst @@ -0,0 +1,272 @@ +REPL +==== + +REPL control serves as an interface to the REPL client. Its configuration has +the following schema: + +.. literalinclude:: ../../../schemas_json/server/control/repl.yaml + :language: yaml + +REPL control works as a Websocket server, using the hat-juggler protocol. It +translates engine's state into JSON serializable data and transfers it to its +clients over local-remote data synchronization. It also provides its clients an +RPC interface, which allows them to send messages causing calls to engine's +functions and receive their results. + +The server listens at the address ``ws://:/ws``, where ``host`` and +``port`` are configuration parameters. After connecting, the clients have +access to the initial state and RPC actions. + +State +----- + +Before accessing the state, clients need to be authorized (described in the +Actions section). The initial state, before login is just an empty JSON object. +After successful login, engine's state is translated into JSON with the +following schema: + +.. code-block:: yaml + + --- + type: object + required: + - actions + - models + properties: + actions: + type: object + patternProperties: + "(.)+": + type: object + description: copied directly from engine's state property + models: + type: object + patternProperties: + "(.)+": + type: object + required: + - type + - id + - instance + properties: + type: + type: string + id: + type: integer + instance: + type: string + description: base64 encoded instance + ... + +RPC interface +------------- + +RPC interface provides several actions its clients may call. These actions are +documented in this section, along with some additional considerations about +result JSON representations and advanced argument passing. + + +Actions +""""""" + +``login`` +''''''''' + +Authorizes the user with given username and password. Successful authorization +gives access to other actions and full state. + +Arguments: + * `username` (``str``) + * `password` (``str``) + +.. note:: Login procedure here is added pro forma and might not provide optimal + level of security for some use cases. If this is the case, it is advised to + develop a separate control that implements a more appropriate procedure. + +``logout`` +'''''''''' + +Logs the user out. + +``create_instance`` +''''''''''''''''''' + +Connects to engine's ``create_instance`` method. Argument preprocessing is +supported. + +Arguments: + * `model_type` (``str``): model type as defined in plugins + * `args` (``List[Any]``): positional arguments passed to the plugin method + * `kwargs` (``Dict[str: Any]``): keyword arguments passed to the plugin + method + +Returns JSON representation of the model. + +``add_instance`` +'''''''''''''''' + +Connects to engine's ``add_instance`` method. + +Arguments: + * `model_type` (``str``): model type as defined in plugins + * `instance` (``str``): base64 encoded serialized model instance + +Returns JSON representation of the model. + +``update_instance`` +''''''''''''''''''' + +Connects to engine's ``update_instance`` method. + +Arguments: + * `model_type` (``str``): model type as defined in plugins + * `instance_id` (``int``): ID of the instance that is being updated + * `instance` (``str``): base64 encoded serialized model instance + +Returns JSON representation of the model. + +``fit`` +''''''' + +Connects to engine's ``fit`` method. Argument preprocessing is supported. + +Arguments: + * `instance_id` (``int``): ID of the instance that is being fitted + * `args` (``List[Any]``): positional arguments for the fitting method + * `kwargs` (``Dict[str, Any]``): keyword arguments for the fitting method + +Returns JSON representation of the model. + +``predict`` +''''''''''' + +Connects to engine's ``predict`` method. Argument preprocessing is supported. + +Arguments: + * `instance_id` (``int``): ID of the instance that is being fitted + * `args` (``List[Any]``): positional arguments for the fitting method + * `kwargs` (``Dict[str, Any]``): keyword arguments for the fitting method + +Returns prediction converted to JSON. + +JSON representations +"""""""""""""""""""" + +Some data structures mentioned in the sections above are, by the default, not +JSON serializable and JSON schema of the structure they take is described in +this section. + +Models schema: + +.. code-block:: yaml + + --- + type: object + required: + - instance_id + - model_type + - instance + properties: + instance_id: + type: integer + model_type: + type: string + instance: + type: string + description: base64 encoded serialized model instance + ... + + +Prediction schema: + + +.. code-block:: yaml + + --- + oneOf: + - {} + - type: object + required: + - type + - data + properties: + type: + enum: + - numpy_array + - pandas_dataframe + - pandas_series + data: + type: object + description: | + prediction result serialized as json, for numpy + arrays and pandas Series, tolist methods are used + and for dataframe, its to_dict method is used + ... + +Argument preprocessing +"""""""""""""""""""""" + +Actions correspond to engine's interface, and that interface provides support +for passing :class:`aimm.server.common.DataAccess` objects, to signify that an +engine action needs to execute a data access plugin before calling the main +action. Control allows sections of messages that contain arguments to take a +specific structure that signify that that argument needs to be converted into +an object. Conversion to :class:`aimm.server.common.DataAccess`, +:class:`numpy.array` and :class:`pandas.DataFrame` is currently supported. The +structure: + +.. code-block:: yaml + + --- + id: '#object_arg' + oneOf: + - {} + - description: converts to common.DataAccess + required: + - name + - args + - kwargs + properties: + type: + const: data_access + name: + type: string + description: data access plugin name + args: + type: array + items: + '$ref': '#object_arg' + kwargs: + patternProperties: + '(.)+': + '$ref': '#object_arg' + - description: converts to numpy.array + required: + - data + - dtype + properties: + type: + const: numpy_array + data: + type: array + dtype: + type: string + - description: converts to pandas.DataFrame + required: + - data + properties: + type: + const: pandas_dataframe + data: + type: object + descirption: result of pandas.DataFrame.to_dict function + - description: converts to pandas.Series + required: + - data + properties: + type: + const: pandas_series + data: + type: object + descirption: result of pandas.Series.tolist function + ... diff --git a/docs/server/engine.rst b/docs/server/engine.rst new file mode 100644 index 0000000..fe0cb15 --- /dev/null +++ b/docs/server/engine.rst @@ -0,0 +1,161 @@ +Engine +====== + +Engine is the central component of the AIMM system. Its purpose is handling +manageable calls to plugins, using the backend when persistence is required and +serving its state to all controllers. + +Engine's part of the configuration needs to satisfy the following schema: + +.. literalinclude:: ../../schemas_json/server/engine.yaml + :language: yaml + +When engine is started, it queries its backend to access all existing model +instances. The instances are then stored in engine's state. Any component +holding a reference to the engine may use it to perform different actions, such +as creating model instances, fitting them or using them for predictions. When a +new model instance is created, or an old one is updated, state change is +notified to any components that have subscribed to state changes. Additionally, +calls to the plugins themselves are manageable and engine keeps theirs states +in its state as well. + +Workflow actions such as fitting or using models are ran asynchronously in +separate asyncio tasks. Additionally, any plugin they call is ran in a separate +process, wrapped in a handler that allows subscriptions to call's state (if the +plugin call supports state notification). The calls can also be cancelled, +which is done using signals - initially SIGTERM and later SIGKILL if a +configured timeout expires. Also, to avoid fork bombs, a separate pseudo-pool +is implemented to serve as an interface for process creation, disallowing +creation of new processes if a certain number of subprocesses is already +running. + +Engine configuration reflects the multiprocessing nature of the implementation, +since all of the options refer to different timeouts and limitations: + + * ``sigterm_timeout`` is the number of seconds waited after SIGTERM was sent + to a process before SIGKILL is sent if it doesn't terminate + * ``max_children`` is the maximum amount of children - concurrent subprocesses + that can run at the same time + * ``check_children_period`` - the check for children counts is done + periodically and this setting indicates how often it is checked + +Engine module provides the following interface: + +.. autoclass:: aimm.server.common.Engine + :members: + +.. autoclass:: aimm.server.common.Action + :members: + +Since there are no strict limitations on the arguments that may be passed to +plugins, i.e., positional and keyword arguments are mostly passed as-is, +callers of the actions have the options of passing different special kinds of +objects as arguments. These objects are interpreted by the engine as subactions +that need to be executed before the main action. E.g., a fitting function may +expect a dataset as an input, and while it is possible to pass the dataset +directly to engine's :meth:`Engine.fit` call, the caller could create a +:class:`aimm.server.common.DataAccess` object and pass it instead. This would +indicate to the engine that it needs to use the data access plugin to access +the required data before fitting. All subactions are also ran in a separate +subprocesses and notify their progress through state. + +State +----- + +State is a dictionary consisting of two properties, ``models`` and ``actions``. +Models are a dictionary with instance IDs as keys and +:class:`aimm.server.common.Model` instances as values. Actions are also a +dictionary, with the following structure: + +.. code-block:: yaml + + --- + description: keys are action IDs + patternProperties: + '(.)+': + oneOf: + - type: 'null' + description: prior to start of the action call + - type: object + required: + - meta + - progress + properties: + meta: + type: object + required: + - call + properties: + call: + type: string + description: call that the action is making + model_type: + type: string + model: + type: integer + args: + type: array + kwargs: + type: object + progress: + enum: + - accessing_data + - executing + - complete + data_access: + type: object + description: | + keys represent argument IDs (numbers for + positional, strings for named), values are set by + plugin's state callbacks + action: + description: set by plugin state callback + ... + +Multiprocessing +--------------- + +The details of the multiprocessing implementation are placed in a separate +module, ``aimm.server.mprocess``. This module is in charge of providing an +interface for managed process calls. The central class of the module is the +:class:`aimm.server.mprocess.ProcessManager`. Its purpose is similar to one of +a standard :class:`multiprocessing.Pool`, main difference being that it does +not keep an exact amount of process workers alive at all times and instead +holds an :class:`asyncio.Condition` that prevents creation of new processes +until the number of children is under the ``max_children`` configuration +parameter. + +The manager is implemented in the following class: + +.. autoclass:: aimm.server.mprocess.ProcessManager + :members: + +The process calls are wrapped in a :class:`aimm.server.mprocess.ProcessHandler` +instance, whose interface allows callers to terminate the process call. It also +allows callers to pass their state change callback functions which are called +whenever the process' state changes. + +After calling :meth:`aimm.server.mprocess.ProcessManager.create_handler` and +receiving a process handler, the call can be made using the +:meth:`aimm.server.mprocess.ProcessHandler.run` function, which, in reality, +first spawns an asyncio task that blocks until the process manager allows +creation of a new process and only then actually creates a new process. + +The state notification is done using callbacks and multiprocessing pipes. +Process handler receives a ``state_cb`` argument in its constructor and this is +the function used to notify states to the rest of the system. It also provides +a method ``proc_notify_state_change``, which is a callback passed to the +function running in the separate process. This function uses a +:class:`multiprocessing.Pipe` object to send function's state values (need to +be pickle-able). Handlers also have internal state listening loops, running in +the main asyncio event loop, that react to receiving these state changes and +notify the rest of the system using the ``state_cb`` passed in the constructor. +Result of the separated process call is also passed through a separate pipe and +set as the result of the :data:`aimm.server.mprocess.ProcessHandler.result` +property. + +The complete class docstring: + +.. autoclass:: aimm.server.mprocess.ProcessHandler + :members: + :noindex: diff --git a/docs/server/index.rst b/docs/server/index.rst new file mode 100644 index 0000000..5a71c13 --- /dev/null +++ b/docs/server/index.rst @@ -0,0 +1,106 @@ +Server +====== + +AIMM server is a server application that listens on one or several interfaces +(web services, multiprocess communication, etc.) and allows the clients that +connect to them to perform various operations with its configured plugins. + +Usage +----- + +After successful installation, the server can be run by calling +``aimm-server`` in the command line. The command has the following interface: + + .. program-output:: python -m aimm.server.main --help + + +Configuration +------------- + +To use the server, it needs to be configured. The configuration is a YAML file +that contains options specific to different components of which the system +consists. It also contains options for logging and Hat configuration. It needs +to validate against the following schema: + +.. literalinclude:: ../../schemas_json/server/main.yaml + :language: yaml + +Logging configuration is mandatory and its values are passed directly to the +:func:`logging.basicConfig` function. Remaining properties of the root part +of the configuration directly correlate to the main components of the server's +architecture, are more complex and will be explained separately. + +Hat +--- + +AIMM server configuration contains options that allow it to work as a part of +`Hat`_ infrastructure. The optional ``hat`` property of the configuration +contains the options that allow this integration. + +.. _hat: https://hat-open.com/ + +Connection to monitor is the minimal requirement in order to integrate to the +Hat network and the configuration parameters are as specified by the `monitor +documentation`_. Additionally, if ``event_server_group`` field is set, AIMM +server will also attempt to connect to event server with in the given group. +If these properties are configured but the server cannot connect to Hat +components, it will halt and wait until it manages to connect. + +.. _monitor documentation: https://hat-monitor.hat-open.com/ + +Since AIMM server supports dynamic imports of different backend and control +implementations that may use the event server connection to receive and +register events, the modules containing these components need to implement a +``get_subscriptions`` function that returns a list of event server +subscriptions. It should have the following type: + +.. autodata:: aimm.server.common.CreateSubscription + + +A combined list of all these subscriptions is then used as the global +subscription of the entire server. If the ``get_subscriptions`` function is +provided, the component implementation will receive an instance of a +:class:`hat.event.eventer.Client`, as specified `here`_. + +.. _here: https://hat-event.hat-open.com/py_api/hat/event/eventer.html#Client + +Architecture +------------ + +After initialization and, optionally, Hat integration, the architecture of AIMM +server may be represented with the following diagram: + +.. image:: server.svg + +The main components are: + + * engine - implements function calls that connect control interfaces with + plugins, keeps the state of the application and uses backend to store it + when neccessary + * backend - handles state data persistence + * plugins - contain model and data access implementations + * control - provides external interfaces for clients, calls engine functions + and notifies clients of state changes or call results + +Common data structures +---------------------- + +The server has some common data structures used in different components, +required as arguments at some methods or returned as their results. They are +documented in this section. + +.. autoclass:: aimm.server.common.Model + :members: +.. autoclass:: aimm.server.common.DataAccess + :members: + + +Components +---------- + +.. toctree:: + :maxdepth: 1 + + engine + backend + control/index diff --git a/docs/server/server.svg b/docs/server/server.svg new file mode 100644 index 0000000..4984ec7 --- /dev/null +++ b/docs/server/server.svg @@ -0,0 +1,3 @@ + + +
Engine
Engine
Control
Control
Backend
Backend
Plugins
Plugins
Hat event interface
Hat event inte...
Hat event interface
Hat event inte...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/dodo.py b/dodo.py new file mode 100644 index 0000000..f6083ad --- /dev/null +++ b/dodo.py @@ -0,0 +1,89 @@ +import os +from hat import json +from pathlib import Path +import subprocess + + +DOIT_CONFIG = {"backend": "sqlite3", "verbosity": 2} + +root_dir = Path(__file__).parent +os.environ["PYTHONPATH"] = ":".join([str(root_dir)]) + + +def task_schemas_json(): + """Build JSON schema repositories""" + + def run(): + repo = json.SchemaRepository(Path("schemas_json/")) + json.encode_file(repo.to_json(), Path("aimm/json_schema_repo.json")) + + return {"actions": [run]} + + +def task_test(): + """Run all tests""" + + def run(args): + args = args or [] + subprocess.run( + [ + "python", + "-m", + "pytest", + "-s", + "-p", + "no:cacheprovider", + "--asyncio-mode", + "auto", + *args, + ], + cwd="test", + check=True, + ) + + return {"actions": [run], "pos_arg": "args", "task_dep": ["schemas_json"]} + + +def task_lint(): + """Check linting""" + + def run(args): + args = args or [] + subprocess.run( + ["flake8", "--exclude", "venv,node_modules,build", ".", *args] + ) + + return {"actions": [run], "pos_arg": "args"} + + +def task_format(): + """Format code""" + + def run(args): + args = args or [] + subprocess.run(["black", ".", "--line-length", "79", *args]) + + return {"actions": [run], "pos_arg": "args"} + + +def task_check(): + """Pre-deployment check""" + return {"actions": [], "task_dep": ["format", "lint"]} + + +def task_docs(): + """Build docs""" + + def run(args): + args = args or [] + subprocess.run(["sphinx-build", "docs", "build/docs", "-q", *args]) + + return {"actions": [run], "pos_arg": "args", "task_dep": ["schemas_json"]} + + +def task_dist(): + """Generate dist""" + return { + "actions": [["poetry", "build"]], + "task_dep": ["schemas_json"], + } diff --git a/examples/0001/.gitignore b/examples/0001/.gitignore new file mode 100644 index 0000000..ecb9cf1 --- /dev/null +++ b/examples/0001/.gitignore @@ -0,0 +1,3 @@ +venv +data +.ipynb_checkpoints diff --git a/examples/0001/Dockerfile b/examples/0001/Dockerfile new file mode 100644 index 0000000..284b3c1 --- /dev/null +++ b/examples/0001/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12 + +WORKDIR /opt/aimm + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY aimm.yaml . + +COPY Iris.ipynb . +COPY plugins ./plugins + +ENV PYTHONPATH=/opt/aimm + +CMD ["aimm-server", "--conf", "aimm.yaml"] diff --git a/examples/0001/Iris.ipynb b/examples/0001/Iris.ipynb new file mode 100644 index 0000000..0d52b1a --- /dev/null +++ b/examples/0001/Iris.ipynb @@ -0,0 +1,232 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f0662eef", + "metadata": {}, + "source": [ + "# Iris \n", + "\n", + "This example contains code snippets that demonstrate how concrete\n", + "implementations of machine learning models may be integratied into\n", + "the AIMM environment as plugins. File `aimm_plugins/plug1.py`\n", + "contains a simple wrapper around sklearn's SVC implementation and we're\n", + "going to use this to host a simple iris-recognition service." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e2cce87c", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Username: user\n", + "Password: ········\n" + ] + } + ], + "source": [ + "from aimm.client import repl\n", + "\n", + "aimm = repl.AIMM()\n", + "await aimm.connect('ws://127.0.0.1:9999')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "89059957", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'models': {}, 'actions': {}}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "aimm.state" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "63cae05e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "aimm.client.repl.Model(instance_id=2)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = await aimm.create_instance('plugins.sklearn_wrapper.SVC')\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2676d30e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'models': {1: aimm.client.repl.Model(instance_id=1),\n", + " 2: aimm.client.repl.Model(instance_id=2)},\n", + " 'actions': {1: {'meta': {'call': 'create_instance',\n", + " 'model_type': 'plugins.sklearn_wrapper.SVC',\n", + " 'args': [],\n", + " 'kwargs': {}},\n", + " 'progress': 'complete'}}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "aimm.state" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "73cd0f9a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n", + " 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n", + " 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await m.fit(repl.DataAccessArg('iris_inputs'), repl.DataAccessArg('iris_outputs'))\n", + "await m.predict(repl.DataAccessArg('iris_inputs'))" + ] + }, + { + "cell_type": "markdown", + "id": "353604ad", + "metadata": {}, + "source": [ + "## Local plugin execution\n", + "\n", + "All plugins may be executed separate from the AIMM server. The following\n", + "cells show how a basic workflow of a machine learning model, starting\n", + "from instantiation, fitting and practical usage - all done through the\n", + "plugins interface. On it's own, this is not particularly interesting -\n", + "after all, it would have easier to achieve the same without using the plugin\n", + "interface and using sklearn's models directly. Still, this shows how\n", + "AIMM server interprets and uses plugins when performing actions." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fcaa916c", + "metadata": {}, + "outputs": [], + "source": [ + "from aimm import plugins" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e57fde25", + "metadata": {}, + "outputs": [], + "source": [ + "plugins.initialize({'names': ['plugins.sklearn_wrapper']})\n", + "svc_type = 'plugins.sklearn_wrapper.SVC'\n", + "model = plugins.exec_instantiate(svc_type)\n", + "\n", + "x = plugins.exec_data_access('iris_inputs')\n", + "y = plugins.exec_data_access('iris_outputs')\n", + "\n", + "model = plugins.exec_fit(svc_type, model, None, x, y)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "142b7362", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "prediction: \n", + "correct: 2\n" + ] + } + ], + "source": [ + "index = 100\n", + "\n", + "print('prediction:', plugins.exec_predict(svc_type, model, None, x[index].reshape(1, -1))[0])\n", + "print('correct:', y[index])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d178df2a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/0001/aimm.yaml b/examples/0001/aimm.yaml new file mode 100644 index 0000000..13f5586 --- /dev/null +++ b/examples/0001/aimm.yaml @@ -0,0 +1,35 @@ +--- +name: AIMM +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + console: + class : logging.StreamHandler + level : INFO + stream : ext://sys.stdout + root: + handlers: + - console + level: INFO + version: 1 +engine: + sigterm_timeout: 5 + max_children: 5 + check_children_period: 3 +backend: + module: aimm.server.backend.sqlite + path: ./data/aimm.db +control: + - module: aimm.server.control.repl + server: + host: 0.0.0.0 + port: 9999 + users: + - username: user + password: d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1 +plugins: + names: + - 'plugins.sklearn_wrapper' +... diff --git a/examples/0001/plugins/sklearn_wrapper.py b/examples/0001/plugins/sklearn_wrapper.py new file mode 100644 index 0000000..abc4d9b --- /dev/null +++ b/examples/0001/plugins/sklearn_wrapper.py @@ -0,0 +1,34 @@ +from aimm import plugins +from sklearn import svm +from sklearn import datasets +import pickle + + +@plugins.data_access("iris_inputs") +def iris_inputs(): + return datasets.load_iris(return_X_y=True)[0] + + +@plugins.data_access("iris_outputs") +def iris_outputs(): + return datasets.load_iris(return_X_y=True)[1] + + +@plugins.model +class SVC(plugins.Model): + def __init__(self, gamma=0.001, C=100.0): + self._svc = svm.SVC(gamma=gamma, C=C) + + def fit(self, X, y): + self._svc = self._svc.fit(X, y) + return self + + def predict(self, X): + return self._svc.predict(X) + + def serialize(self): + return pickle.dumps(self) + + @classmethod + def deserialize(cls, instance_bytes): + return pickle.loads(instance_bytes) diff --git a/examples/0001/readme.rst b/examples/0001/readme.rst new file mode 100644 index 0000000..a808e19 --- /dev/null +++ b/examples/0001/readme.rst @@ -0,0 +1,31 @@ +Iris +==== + +This example contains a minimal implementation of the standard iris classifier, +hosted on an AIMM server. Plugins directory contains a single plugin +implementation, one that serves as a wrapper around existing sklearn Support +Vector Classifier (SVC) implementation. To run this example, install the +requirements, add this directory to the PYTHONPATH environment variable and +call:: + + aimm-server --conf aimm.yaml + +While the server is running, start a jupyter notebook process and host the +``Iris.ipynb`` notebook. The notebook shows examples of how AIMM service can be +used to run plugins. + +When prompted for login, the username is ``user`` and password is ``pass``. + +Running through docker +---------------------- + +The example can also be ran using docker with following steps: + +* Build the image +* Running the image starts the AIMM server which uses port 9999 for its REPL + interface, make sure that port is mapped +* Run ``jupyter notebook``, open the Iris notebook and call commands in it. + * Optionally, jupyter notebook can be started through the same container AIMM + server is running in with + ``docker exec jupyter notebook --allow-root --ip 0.0.0.0``. + Make sure port 8888 is exposed. diff --git a/examples/0001/requirements.txt b/examples/0001/requirements.txt new file mode 100644 index 0000000..90a7f1c --- /dev/null +++ b/examples/0001/requirements.txt @@ -0,0 +1,127 @@ +aimm==1.2.dev0 +aiohappyeyeballs==2.4.0 +aiohttp==3.10.5 +aiosignal==1.3.1 +anyio==4.4.0 +appdirs==1.4.4 +argon2-cffi==23.1.0 +argon2-cffi-bindings==21.2.0 +arrow==1.3.0 +asttokens==2.4.1 +async-lru==2.0.4 +attrs==24.2.0 +babel==2.16.0 +beautifulsoup4==4.12.3 +bleach==6.1.0 +certifi==2024.8.30 +cffi==1.17.1 +charset-normalizer==3.3.2 +comm==0.2.2 +debugpy==1.8.5 +decorator==5.1.1 +defusedxml==0.7.1 +executing==2.1.0 +fastjsonschema==2.20.0 +fqdn==1.5.1 +frozenlist==1.4.1 +h11==0.14.0 +hat-aio==0.7.10 +hat-asn1==0.6.8 +hat-drivers==0.8.9 +hat-event==0.9.20 +hat-json==0.5.28 +hat-juggler==0.6.21 +hat-monitor==0.8.11 +hat-peg==0.5.9 +hat-sbs==0.7.2 +hat-util==0.6.16 +httpcore==1.0.5 +httpx==0.27.2 +idna==3.10 +ipykernel==6.29.5 +ipython==8.27.0 +ipywidgets==8.1.5 +isoduration==20.11.0 +jedi==0.19.1 +Jinja2==3.1.4 +joblib==1.4.2 +json5==0.9.25 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.22.0 +jsonschema-specifications==2023.12.1 +jupyter==1.1.1 +jupyter-console==6.6.3 +jupyter-events==0.10.0 +jupyter-lsp==2.2.5 +jupyter_client==8.6.2 +jupyter_core==5.7.2 +jupyter_server==2.14.2 +jupyter_server_terminals==0.5.3 +jupyterlab==4.2.5 +jupyterlab_pygments==0.3.0 +jupyterlab_server==2.27.3 +jupyterlab_widgets==3.0.13 +lmdb==1.4.1 +MarkupSafe==2.1.5 +matplotlib-inline==0.1.7 +mistune==3.0.2 +multidict==6.1.0 +nbclient==0.10.0 +nbconvert==7.16.4 +nbformat==5.10.4 +nest-asyncio==1.6.0 +notebook==7.2.2 +notebook_shim==0.2.4 +numpy==2.1.1 +overrides==7.7.0 +packaging==24.1 +pandas==2.2.2 +pandocfilters==1.5.1 +parso==0.8.4 +pexpect==4.9.0 +platformdirs==4.3.3 +prometheus_client==0.20.0 +prompt_toolkit==3.0.47 +psutil==6.0.0 +ptyprocess==0.7.0 +pure_eval==0.2.3 +pycparser==2.22 +Pygments==2.18.0 +pyserial==3.5 +python-dateutil==2.9.0.post0 +python-json-logger==2.0.7 +pytz==2024.2 +PyYAML==6.0.2 +pyzmq==26.2.0 +referencing==0.35.1 +requests==2.32.3 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rpds-py==0.20.0 +scikit-learn==1.5.2 +scipy==1.14.1 +Send2Trash==1.8.3 +setuptools==75.1.0 +six==1.16.0 +sniffio==1.3.1 +soupsieve==2.6 +stack-data==0.6.3 +tenacity==9.0.0 +terminado==0.18.1 +threadpoolctl==3.5.0 +tinycss2==1.3.0 +tomli==2.0.1 +tomli_w==1.0.0 +tornado==6.4.1 +traitlets==5.14.3 +types-python-dateutil==2.9.0.20240906 +tzdata==2024.1 +uri-template==1.3.0 +urllib3==2.2.3 +wcwidth==0.2.13 +webcolors==24.8.0 +webencodings==0.5.1 +websocket-client==1.8.0 +widgetsnbextension==4.0.13 +yarl==1.11.1 diff --git a/examples/0002/.gitignore b/examples/0002/.gitignore new file mode 100644 index 0000000..7b65fef --- /dev/null +++ b/examples/0002/.gitignore @@ -0,0 +1,4 @@ +/venv +/data +/src_js +/view/login/index.js diff --git a/examples/0002/Dockerfile b/examples/0002/Dockerfile new file mode 100644 index 0000000..28755d9 --- /dev/null +++ b/examples/0002/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12 + +WORKDIR /opt/aimm + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY hat.sh . +COPY aimm.sh . + +COPY conf ./conf +COPY src_py ./src_py +COPY view ./view + +CMD ["./hat.sh"] diff --git a/examples/0002/aimm.sh b/examples/0002/aimm.sh new file mode 100755 index 0000000..8550c3d --- /dev/null +++ b/examples/0002/aimm.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +export PYTHONPATH=src_py + +aimm-server --conf conf/aimm.yaml diff --git a/examples/0002/conf/aimm.yaml b/examples/0002/conf/aimm.yaml new file mode 100644 index 0000000..7dca63e --- /dev/null +++ b/examples/0002/conf/aimm.yaml @@ -0,0 +1,42 @@ +--- +name: aimm +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + comm_type: TCP + formatter: default + host: 127.0.0.1 + level: INFO + port: 6514 + queue_size: 10 + root: + handlers: + - syslog + level: INFO + version: 1 +engine: + sigterm_timeout: 5 + max_children: 5 + check_children_period: 3 +backend: + module: aimm.server.backend.event + model_prefix: ['aimm', 'model'] +control: + - module: aimm.server.control.event + event_prefixes: + create_instance: ['aimm', 'create_instance'] + predict: ['aimm', 'predict'] + state_event_type: ['aimm', 'state'] + action_state_event_type: ['aimm', 'response'] +plugins: + names: + - aimm_plugins.power +hat: + eventer_server: + host: 127.0.0.1 + port: 23012 +... diff --git a/examples/0002/conf/event.yaml b/examples/0002/conf/event.yaml new file mode 100644 index 0000000..5e32415 --- /dev/null +++ b/examples/0002/conf/event.yaml @@ -0,0 +1,38 @@ +--- +name: event +server_id: 0 +backend: + db_path: data/event.db + identifier: null + module: hat.event.backends.lmdb + flush_period: 5 + cleanup_period: 5 + conditions: [] + latest: + subscriptions: + - ['*'] + timeseries: [] +modules: + - module: scada.module +eventer_server: + host: 127.0.0.1 + port: 23012 +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + comm_type: TCP + formatter: default + host: 127.0.0.1 + level: INFO + port: 6514 + queue_size: 10 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0002/conf/gateway.yaml b/examples/0002/conf/gateway.yaml new file mode 100644 index 0000000..79aac5a --- /dev/null +++ b/examples/0002/conf/gateway.yaml @@ -0,0 +1,28 @@ +--- +name: gateway +event_server: + eventer_server: + host: 127.0.0.1 + port: 23012 +devices: + - module: scada.device + name: device +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + comm_type: TCP + formatter: default + host: 127.0.0.1 + level: INFO + port: 6514 + queue_size: 10 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0002/conf/gui.yaml b/examples/0002/conf/gui.yaml new file mode 100644 index 0000000..3de354d --- /dev/null +++ b/examples/0002/conf/gui.yaml @@ -0,0 +1,47 @@ +--- +name: gui +event_server: + eventer_server: + host: 127.0.0.1 + port: 23012 +address: + host: 0.0.0.0 + port: 23023 +adapters: + - module: scada.adapter + name: adapter +views: + - name: login + builtin: login + conf: null + - name: grid + view_path: ./view/grid + conf: null +initial_view: login +users: + - name: user + password: + hash: cef3cf37b4fa2a692f06d6e637e112bd3a37179a8c6752c115ff21813a816574 + salt: 497b53087260002cd62ffabc94267437 + roles: + - user_role + view: grid +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + comm_type: TCP + formatter: default + host: 127.0.0.1 + level: INFO + port: 6514 + queue_size: 10 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0002/conf/orchestrator.yaml b/examples/0002/conf/orchestrator.yaml new file mode 100644 index 0000000..538327e --- /dev/null +++ b/examples/0002/conf/orchestrator.yaml @@ -0,0 +1,69 @@ +--- +components: +- args: + - hat-syslog-server + - --db-path + - ./data/syslog.db + delay: 0 + name: syslog + revive: false + start_delay: 1 + create_timeout: 5 + sigint_timeout: 2 + sigkill_timeout: 3 +- args: + - hat-event-server + - --conf + - ./conf/event.yaml + delay: 1 + name: event + revive: false + start_delay: 1 + create_timeout: 5 + sigint_timeout: 2 + sigkill_timeout: 3 +- args: + - hat-gateway + - --conf + - ./conf/gateway.yaml + delay: 2 + name: gateway + revive: false + start_delay: 1 + create_timeout: 5 + sigint_timeout: 2 + sigkill_timeout: 3 +- args: + - hat-gui-server + - --conf + - ./conf/gui.yaml + delay: 3 + name: gui + revive: false + start_delay: 1 + create_timeout: 5 + sigint_timeout: 2 + sigkill_timeout: 3 +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + comm_type: TCP + formatter: default + host: 127.0.0.1 + level: INFO + port: 6514 + queue_size: 10 + root: + handlers: + - syslog + level: INFO + version: 1 +type: orchestrator +ui: + host: 0.0.0.0 + port: 23021 +... diff --git a/examples/0002/hat.sh b/examples/0002/hat.sh new file mode 100755 index 0000000..c6abe11 --- /dev/null +++ b/examples/0002/hat.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +export PYTHONPATH=src_py + +mkdir -p data +python ./src_py/simulation.py & +python -m hat.orchestrator.main --conf ./conf/orchestrator.yaml diff --git a/examples/0002/readme.rst b/examples/0002/readme.rst new file mode 100644 index 0000000..1dced72 --- /dev/null +++ b/examples/0002/readme.rst @@ -0,0 +1,37 @@ +Hat integration +=============== + +This example shows the way AIMM server can be integrated into `Hat +infrastructure `_. The example contains a minimal +SCADA-like interface that connects to a simulated power grid. The measurements +from the power grid are displayed in the GUI available at address +``localhost:23023``. If the AIMM server is ran, new information is available in +the GUI, showing estimated measurements derived from the ones that are +available, solving the problem of state estimation. + +To run, first install the requirements from ``requirements.txt`` and add the +``src_py`` directory to the PYTHONPATH environment variable. Then, run the +simulation script, ``./src_py/simulation.py``. Next step is running the Hat +SCADA, which can be done using the ``./hat.sh`` script. The GUI should now be +available at ``localhost:23023``. Lastly, run the AIMM server by running the +``./aimm.sh`` script. After running the AIMM server, its estimations will also +be visible in the GUI. + +When prompted for login, the username is ``user`` and the password is ``pass``. + +Running in docker container +--------------------------- + +The process is similar to running directly on host, main difference is that some of the +ports need to be mapped. All processes should be ran in the same container. + +#. Build the image with ``docker build`` +#. Default command will start hat, relevant ports: + * ``23020``: SysLog UI - system logs of all components + * ``23021``: Orchestrator UI - active Hat processes + * ``23023``: GUI - interface where measurements and estimations are shown +#. Simulation is started separately with command + ``docker exec python ./src_py/simulation.py`` +#. Measurements should be visible on the GUI +#. AIMM is started separately with command ``docker exec ./aimm.sh`` +#. Estimations are shown alongside measurements in the GUI diff --git a/examples/0002/requirements.txt b/examples/0002/requirements.txt new file mode 100644 index 0000000..9238d19 --- /dev/null +++ b/examples/0002/requirements.txt @@ -0,0 +1,52 @@ +aimm==1.2.dev0 +aiohappyeyeballs==2.4.0 +aiohttp==3.10.5 +aiosignal==1.3.1 +appdirs==1.4.4 +attrs==24.2.0 +deepdiff==8.0.1 +frozenlist==1.4.1 +hat-aio==0.7.10 +hat-asn1==0.6.8 +hat-drivers==0.8.9 +hat-event==0.9.20 +hat-gateway==0.6.11 +hat-gui==0.7.9 +hat-json==0.5.28 +hat-juggler==0.6.21 +hat-monitor==0.8.12 +hat-orchestrator==0.7.4 +hat-peg==0.5.9 +hat-sbs==0.7.2 +hat-syslog==0.7.18 +hat-util==0.6.16 +idna==3.10 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.22.0 +jsonschema-specifications==2023.12.1 +llvmlite==0.43.0 +lmdb==1.4.1 +multidict==6.1.0 +networkx==3.3 +numba==0.60.0 +numpy==1.26.4 +orderly-set==5.2.2 +packaging==24.1 +pandapower==2.14.11 +pandas==2.2.2 +psutil==6.0.0 +pyserial==3.5 +python-dateutil==2.9.0.post0 +pytz==2024.2 +PyYAML==6.0.2 +referencing==0.35.1 +rpds-py==0.20.0 +scipy==1.13.1 +six==1.16.0 +tenacity==9.0.0 +tomli==2.0.1 +tomli_w==1.0.0 +tqdm==4.66.5 +tzdata==2024.1 +yarl==1.11.1 diff --git a/examples/0002/src_py/aimm_plugins/__init__.py b/examples/0002/src_py/aimm_plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/0002/src_py/aimm_plugins/power.py b/examples/0002/src_py/aimm_plugins/power.py new file mode 100644 index 0000000..c7df7d4 --- /dev/null +++ b/examples/0002/src_py/aimm_plugins/power.py @@ -0,0 +1,39 @@ +import pandapower.estimation +import pandapower.networks +import time + +from aimm import plugins + + +@plugins.model +class StateEstimator(plugins.Model): + def __init__(self): + pass + + def fit(self): + pass + + def predict(self, measurements): + network = pandapower.networks.case14() + pandapower.create.create_measurement(network, "v", "bus", 1, 0.05, 0) + for measurement in measurements: + pandapower.create.create_measurement( + network, + measurement["type"], + measurement["element_type"], + measurement["value"], + measurement["std_dev"], + measurement["element"], + side=measurement.get("side"), + ) + + pandapower.estimation.estimate(network) + time.sleep(0.5) + return network.res_bus_est.to_dict() + + def serialize(self): + return bytes() + + @classmethod + def deserialize(cls, instance_bytes): + return StateEstimator() diff --git a/examples/0002/src_py/scada/adapter.py b/examples/0002/src_py/scada/adapter.py new file mode 100644 index 0000000..89098de --- /dev/null +++ b/examples/0002/src_py/scada/adapter.py @@ -0,0 +1,71 @@ +import logging + +from hat import aio +from hat import util +from hat import json +from hat.gui import common +import hat.event.common + + +mlog = logging.getLogger(__name__) + + +def create_subscription(_): + return hat.event.common.create_subscription( + [("measurement", "?", "?"), ("estimation", "?", "?")] + ) + + +class Adapter(common.Adapter): + def __init__(self, _, event_client): + self._state = {"measurement": [], "estimation": []} + self._event_client = event_client + self._change_cbs = util.CallbackRegistry() + self._group = aio.Group() + + @property + def async_group(self): + return self._group + + @property + def state(self): + return self._state + + async def process_events(self, events): + for event in events: + reading_kind, index, measurement_kind = event.type + self._state = json.set_( + self._state, + [reading_kind, int(index), measurement_kind], + event.payload.data, + ) + self._change_cbs.notify() + + async def create_session(self, user, roles, state, notify_cb): + session = Session(self, state, self._group.create_subgroup()) + self._change_cbs.register(session.on_change) + return session + + +class Session(common.AdapterSession): + def __init__(self, adapter, state, group): + self._adapter = adapter + self._state = state + self._group = group + + self.on_change() + + @property + def async_group(self): + return self._group + + def process_request(self, name, data): + pass + + def on_change(self): + self._state.set([], self._adapter.state) + + +info = common.AdapterInfo( + create_subscription=create_subscription, create_adapter=Adapter +) diff --git a/examples/0002/src_py/scada/device.py b/examples/0002/src_py/scada/device.py new file mode 100644 index 0000000..e09e108 --- /dev/null +++ b/examples/0002/src_py/scada/device.py @@ -0,0 +1,73 @@ +import logging +import asyncio + +from hat import aio +import hat.event.common +import hat.gateway.common + +from hat.drivers import iec104 +from hat.drivers import tcp + + +mlog = logging.getLogger(__name__) + + +class Device(hat.gateway.common.Device): + + def __init__(self, _, client, __): + self._async_group = aio.Group() + self._event_client = client + self._async_group.spawn(self._connection_loop) + + @property + def async_group(self): + return self._async_group + + async def process_events(self, events): + pass + + async def _connection_loop(self): + try: + connection = None + while True: + try: + connection = await iec104.connect( + addr=tcp.Address("127.0.0.1", 20001) + ) + except Exception as e: + mlog.error("connect failed %s", e, exc_info=e) + if not connection or connection.is_closed: + await asyncio.sleep(3) + continue + self._conn_group = self._async_group.create_subgroup() + self._conn_group.spawn(self._receive_loop, connection) + self._conn_group.spawn( + aio.call_on_cancel, connection.async_close + ) + try: + await connection.wait_closed() + finally: + connection = None + await self._conn_group.async_close() + finally: + self._async_group.close() + + async def _receive_loop(self, connection): + while True: + data_list = await connection.receive() + await self._event_client.register( + [_data_to_event(i) for i in data_list] + ) + + +info = hat.gateway.common.DeviceInfo(type="device", create=Device) + + +def _data_to_event(data): + bus_id = data.asdu_address + measurement_type = ["p", "q", "v", "va"][data.io_address] + return hat.event.common.RegisterEvent( + type=("measurement", str(bus_id), measurement_type), + source_timestamp=None, + payload=hat.event.common.EventPayloadJson(data.data.value), + ) diff --git a/examples/0002/src_py/scada/module.py b/examples/0002/src_py/scada/module.py new file mode 100644 index 0000000..d788447 --- /dev/null +++ b/examples/0002/src_py/scada/module.py @@ -0,0 +1,169 @@ +from hat import aio +from hat import json +from hat.event import common +import asyncio +import itertools +import logging + + +mlog = logging.getLogger(__name__) + + +class Module(common.Module): + def __init__(self, _, engine, source): + self._gw_prefix = ("gateway", "gateway", "device", "device") + self._subscription = common.create_subscription( + [ + ("measurement", "?", "?"), + ("event", "?", "eventer", "gateway/gateway"), + ("aimm", "state"), + ("aimm", "response"), + ] + ) + + self._source = source + + self._async_group = aio.Group() + self._engine = engine + self._model_id = None + self._create_model_request_id = None + self._predict_request_id = None + self._measurements = {} + + self._predict_task = None + self._request_gen = itertools.count(1) + + @property + def async_group(self): + return self._async_group + + @property + def subscription(self): + return self._subscription + + async def process(self, source, e): + return [ + event async for event in self._async_generator_process(source, e) + ] + + async def _async_generator_process(self, source, e): + if source == self._source: + return + + payload = e.payload.data + if e.type == ("event", "0", "eventer", "gateway/gateway"): + if payload == "CONNECTED": + yield _register_event( + ( + "gateway", + "device", + "example", + "system", + "enable", + ), + True, + ) + + elif e.type[0] == "measurement": + self._measurements = json.set_( + self._measurements, list(e.type[1:]), payload + ) + if self._predict_task is None: + self._predict_task = self.async_group.spawn( + self._predict, self._source + ) + + elif e.type == ("aimm", "state"): + if self._model_id is not None: + return + if self._model_id in payload["models"]: + return + if self._create_model_request_id is not None: + return + + self._model_id = None + self._create_model_request_id = str(next(self._request_gen)) + request_ev = _register_event( + ("aimm", "create_instance"), + { + "model_type": "aimm_plugins.power.StateEstimator", + "args": [], + "kwargs": {}, + "request_id": self._create_model_request_id, + }, + ) + yield request_ev + + elif e.type == ("aimm", "response"): + if payload["request_id"] == self._create_model_request_id: + self._model_id = str(payload["result"]) + elif payload["request_id"] == self._predict_request_id: + result = payload["result"] + if result is None: + return + bus_ids = set(result["vm_pu"]) + bus_ids |= set(result["va_degree"]) + bus_ids |= set(result["p_mw"]) + bus_ids |= set(result["q_mvar"]) + for bus_id in bus_ids: + yield _register_event( + ("estimation", bus_id, "v"), result["vm_pu"][bus_id] + ) + yield _register_event( + ("estimation", bus_id, "va"), + result["va_degree"][bus_id], + ) + yield _register_event( + ("estimation", bus_id, "p"), result["p_mw"][bus_id] + ) + yield _register_event( + ("estimation", bus_id, "q"), result["q_mvar"][bus_id] + ) + + async def _predict(self, source): + await asyncio.sleep(1) # buffer measurements + try: + if self._model_id is None: + return + self._predict_request_id = str(next(self._request_gen)) + await self._engine.register( + source, + [ + _register_event( + ("aimm", "predict", self._model_id), + { + "args": [ + list(_measurements_to_arg(self._measurements)) + ], + "kwargs": {}, + "request_id": self._predict_request_id, + }, + ) + ], + ) + finally: + self._predict_task = None + + +def _measurements_to_arg(measurements): + for bus_id, bus_measurements in measurements.items(): + for m_type, value in bus_measurements.items(): + yield { + "type": m_type, + "element_type": "bus", + "value": value, + "std_dev": 10, + "element": int(bus_id), + "side": None, + } + + +info = common.ModuleInfo(create=Module) + + +def _register_event(event_type, payload): + return common.RegisterEvent( + type=event_type, + source_timestamp=None, + payload=common.EventPayloadJson(payload), + ) diff --git a/examples/0002/src_py/simulation.py b/examples/0002/src_py/simulation.py new file mode 100644 index 0000000..e8044d5 --- /dev/null +++ b/examples/0002/src_py/simulation.py @@ -0,0 +1,80 @@ +from hat import aio +from hat.drivers import iec104 +from hat.drivers import tcp +import asyncio +import pandapower.networks +import random +import sys + + +def main(): + aio.init_asyncio() + aio.run_asyncio(async_main()) + + +async def async_main(): + connections = set() + + def connection_cb(new_connection): + connections.add(new_connection) + new_connection.async_group.spawn( + aio.call_on_cancel, lambda: connections.remove(new_connection) + ) + + server = await iec104.listen( + connection_cb, tcp.Address("127.0.0.1", 20001) + ) + try: + while True: + await asyncio.sleep(10) + if not connections: + continue + + net = pandapower.networks.case14() + for load_id in range(len(net.load)): + load = net.load.loc[load_id] + net.load.loc[load_id, "p_mw"] = _randomize(load.p_mw) + net.load.loc[load_id, "q_mvar"] = _randomize( + max(load.q_mvar, 0.1) + ) + for gen_id in range(len(net.gen)): + gen = net.gen.loc[gen_id] + net.gen.loc[gen_id, "p_mw"] = _randomize(gen.p_mw) + net.gen.loc[gen_id, "vm_pu"] = _randomize(gen.vm_pu) + + pandapower.runpp(net) + + data = [] + for bus in net.res_bus.iloc: + data.append(_get_msg(int(bus.name), 0, bus.p_mw)) + data.append(_get_msg(int(bus.name), 1, bus.q_mvar)) + + for connection in connections: + await connection.send(data) + finally: + server.close() + for c in connections: + c.close() + + +def _randomize(ref): + return random.uniform(ref * 0.75, ref * 1.25) + + +def _get_msg(asdu, io, value): + return iec104.DataMsg( + asdu_address=asdu, + io_address=io, + data=iec104.FloatingData( + value=iec104.FloatingValue(value), + quality=iec104.MeasurementQuality(*[False] * 5), + ), + time=None, + cause=iec104.DataResCause.SPONTANEOUS, + is_test=False, + originator_address=0, + ) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/0002/view/grid/index.js b/examples/0002/view/grid/index.js new file mode 100644 index 0000000..fc47ecf --- /dev/null +++ b/examples/0002/view/grid/index.js @@ -0,0 +1,149 @@ +/* + * ATTENTION: An "eval-source-map" devtool has been used. + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ([ +/* 0 */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"init\": () => (/* binding */ init),\n/* harmony export */ \"vt\": () => (/* binding */ vt),\n/* harmony export */ \"destroy\": () => (/* binding */ destroy)\n/* harmony export */ });\n/* harmony import */ var _hat_core_renderer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);\n\n\n\nasync function init() {\n await _hat_core_renderer__WEBPACK_IMPORTED_MODULE_0__.default.set('view', {});\n}\n\n\nfunction vt() {\n return ['div',\n ['div', {\n props: {\n style: 'margin-bottom: 10px'}},\n 'The following example demonstrates usage of the AIMM server for state estimation. A simple power grid ',\n 'based on IEEE 14-bus case was modeled and simulated. Simulated measurements are passed to the AIMM ',\n 'server, which generates estimations for existing measurements and the missing ones. Measurements are ',\n 'represented with the green color and estimations are blue. If an estimation or a measurement is not ',\n 'available, that is represented with the grey color. If you are seeing this interface, that means that ',\n 'hat is set up and running. If measurements (first two P and Q labels of every bus) are missing, make ',\n 'sure that the simulator is started. If estimations are missing, make sure that the AIMM server is ',\n 'started. For more instructions or running the individual components, consult the readme of this example.',\n ],\n ['svg', {\n attrs: { viewBox: \"0 -5 1500 700\", stroke: 'black', fill: '#00000000'}},\n\n text(50, 25, 'BUS 1'),\n measurements(100, 5, 0),\n bus(50, 90, 100),\n ext(50, 50),\n powerLine([60, 70], [60, 90]),\n powerLine([65, 90], [65, 220]),\n powerLine([65, 100], [315, 100], [315, 220]),\n\n text(75, 180, 'BUS 5'),\n measurements(130, 135, 4),\n bus(50, 220, 100),\n load(75, 230),\n trafo(60, 220, 350),\n powerLine([85, 220], [85, 230]),\n\n text(325, 180, 'BUS 2'),\n measurements(380, 135, 1),\n bus(300, 220, 100),\n load(325, 230),\n generator(350, 230),\n powerLine([315, 220], [315, 350]),\n powerLine([385, 220], [385, 225], [515, 225], [515, 350]),\n powerLine([335, 220], [335, 230]),\n powerLine([360, 220], [360, 230]),\n\n text(75, 310, 'BUS 6'),\n measurements(130, 265, 5),\n bus(50, 350, 100),\n load(75, 360),\n generator(100, 360),\n powerLine([65, 350], [65, 580]),\n powerLine([65, 480], [115, 480], [115, 490]),\n powerLine([65, 530], [330, 530], [330, 580]),\n powerLine([85, 350], [85, 360]),\n powerLine([110, 350], [110, 360]),\n\n text(325, 310, 'BUS 3'),\n measurements(380, 265, 2),\n bus(300, 350, 100),\n load(325, 360),\n generator(350, 360),\n powerLine([385, 350], [385, 360], [510, 360], [510, 350]),\n powerLine([335, 350], [335, 360]),\n powerLine([360, 350], [360, 360]),\n\n text(525, 310, 'BUS 4'),\n measurements(580, 265, 3),\n bus(500, 350, 100),\n load(530, 360),\n trafo(515, 350, 490),\n powerLine([585, 350], [585, 360], [710, 360]),\n trafo(710, 360, 490),\n powerLine([540, 350], [540, 360]),\n\n text(75, 450, 'BUS 11'),\n measurements(130, 405, 10),\n bus(100, 490, 100),\n load(125, 500),\n powerLine([185, 490], [185, 500], [315, 500], [315, 490]),\n powerLine([135, 490], [135, 500]),\n\n text(325, 450, 'BUS 10'),\n measurements(380, 405, 9),\n bus(300, 490, 100),\n load(325, 500),\n powerLine([385, 490], [385, 500], [515, 500], [515, 490]),\n powerLine([335, 490], [335, 500]),\n\n text(535, 450, 'BUS 9'),\n measurements(590, 405, 8),\n bus(500, 490, 100),\n load(530, 500),\n powerLine([585, 490], [585, 500]),\n trafoHorizontal(585, 715, 500),\n powerLine([715, 500], [715, 490]),\n powerLine([580, 490], [580, 580]),\n powerLine([540, 490], [540, 500]),\n\n text(735, 450, 'BUS 7'),\n measurements(790, 405, 6),\n bus(700, 490, 100),\n trafo(720, 490, 580),\n\n text(25, 640, 'BUS 12'),\n measurements(90, 605, 11),\n bus(50, 580, 100),\n load(80, 540),\n powerLine([135, 580], [135, 590], [315, 590], [315, 580]),\n powerLine([90, 580], [90, 560]),\n\n text(280, 640, 'BUS 13'),\n measurements(340, 605, 12),\n bus(300, 580, 100),\n load(340, 540),\n powerLine([385, 580], [385, 590], [515, 590], [515, 580]),\n powerLine([350, 580], [350, 560]),\n\n text(480, 640, 'BUS 14'),\n measurements(540, 605, 13),\n bus(500, 580, 100),\n load(530, 540),\n powerLine([540, 580], [540, 560]),\n\n text(680, 640, 'BUS 8'),\n measurements(740, 605, 7),\n bus(700, 580, 100),\n generator(755, 540),\n powerLine([765, 580], [765, 560])\n ]\n ];\n}\n\n\nfunction bus(x1, y, width){\n return ['line', {\n attrs: {x1: x1, y1: y, x2: x1 + width, y2: y, 'stroke-width': 4}}];\n}\n\n\nfunction powerLine(...points){\n const vts = [];\n for (let i = 1; i < points.length; i++) {\n vts.push(['line', {\n attrs: {\n x1: points[i-1][0],\n y1: points[i-1][1],\n x2: points[i][0],\n y2: points[i][1],\n }}])\n }\n return ['g', vts];\n}\n\n\nfunction trafo(x, y1, y2) {\n const r = 10\n\n const height = Math.abs(y2 - y1);\n const cy1 = y1 + height / 2 - r * 1.5;\n const cy2 = y2 - height / 2 + r * 1.5;\n return ['g',\n ['line', {attrs: { x1: x, y1: y1, x2: x, y2: cy1 }}],\n ['circle', {attrs: {cx: x, cy: cy1 + r, r: r}}],\n ['circle', {attrs: {cx: x, cy: cy2 - r, r: r}}],\n ['line', {attrs: { x1: x, y1: cy2, x2: x, y2: y2 }}]\n ];\n}\n\n\nfunction trafoHorizontal(x1, x2, y) {\n const r = 10\n\n const width = Math.abs(x1 - x2);\n const cx1 = x1 + width / 2 - r * 1.5;\n const cx2 = x2 - width / 2 + r * 1.5;\n return ['g',\n ['line', {attrs: { x1: x1, y1: y, x2: cx1, y2: y }}],\n ['circle', {attrs: {cx: cx1 + r, cy: y, r: r}}],\n ['circle', {attrs: {cx: cx2 - r, cy: y, r: r}}],\n ['line', {attrs: { x1: cx2, y1: y, x2: x2, y2: y }}]\n ];\n}\n\n\nfunction generator(x, y) {\n return rectLetter(x, y, 'G');\n}\n\n\nfunction load(x, y) {\n return rectLetter(x, y, 'L');\n}\n\n\nfunction ext(x, y) {\n return rectLetter(x, y, 'E');\n}\n\n\nfunction rectLetter(x, y, letter) {\n return ['g',\n ['rect', {attrs: {x: x, y: y, width: 20, height: 20}}],\n text(x + 5, y + 15, letter)\n ];\n}\n\n\nfunction measurementItem(x, y, prefix, suffix, busId, type) {\n return processItem(x, y, prefix, suffix, 'green', ['measurement', busId, type]);\n}\n\n\nfunction estimationItem(x, y, prefix, suffix, busId, type) {\n return processItem(x, y, prefix, suffix, 'blue', ['estimation', busId, type]);\n}\n\n\nfunction processItem(x, y, prefix, suffix, color, path) {\n const value = _hat_core_renderer__WEBPACK_IMPORTED_MODULE_0__.default.get('remote', 'adapter', ...path);\n return text(\n x, y, `${prefix} ${value != null ? Math.round(value * 100) / 100 : '????'} ${suffix}`, 'smaller',\n value != null ? color : 'gray');\n}\n\n\nfunction measurements(x, y, busId) {\n return ['g',\n measurementItem(x, y, 'P:', 'MW', busId, 'p'),\n measurementItem(x, y + 15, 'Q:', 'mVar', busId, 'q'),\n estimationItem(x, y + 30, 'P:', 'MW', busId, 'p'),\n estimationItem(x, y + 45, 'Q:', 'mVar', busId, 'q'),\n estimationItem(x, y + 60, 'V:', 'p.u.', busId, 'v'),\n estimationItem(x - 5, y + 75, 'Va:', '°', busId, 'va'),\n ]\n}\n\n\nfunction text(x, y, value, fontSize = 'default', stroke = 'black') {\n return ['text', {attrs: {x: x, y: y, 'font-size': fontSize, stroke: stroke}}, value]\n}\n\n\nfunction destroy() {}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9tb2R1bGVzL2luZGV4LmpzPzJkODciXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFtQzs7O0FBRzVCO0FBQ1AsVUFBVSwyREFBSyxXQUFXO0FBQzFCOzs7QUFHTztBQUNQO0FBQ0E7QUFDQTtBQUNBLDhDQUE4QztBQUM5QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQiwrREFBK0Q7O0FBRW5GO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0EsZ0JBQWdCLHlEQUF5RDtBQUN6RTs7O0FBR0E7QUFDQTtBQUNBLG1CQUFtQixtQkFBbUI7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixRQUFRLGlDQUFpQztBQUMzRCxvQkFBb0IsUUFBUSwwQkFBMEI7QUFDdEQsb0JBQW9CLFFBQVEsMEJBQTBCO0FBQ3RELGtCQUFrQixRQUFRLGlDQUFpQztBQUMzRDtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLFFBQVEsaUNBQWlDO0FBQzNELG9CQUFvQixRQUFRLDBCQUEwQjtBQUN0RCxvQkFBb0IsUUFBUSwwQkFBMEI7QUFDdEQsa0JBQWtCLFFBQVEsaUNBQWlDO0FBQzNEO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQSxrQkFBa0IsUUFBUSxtQ0FBbUM7QUFDN0Q7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQSxrQkFBa0IsMkRBQUs7QUFDdkI7QUFDQSxpQkFBaUIsT0FBTyxHQUFHLHVEQUF1RCxHQUFHLE9BQU87QUFDNUY7QUFDQTs7O0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0EscUJBQXFCLFFBQVEsbURBQW1EO0FBQ2hGOzs7QUFHTyIsImZpbGUiOiIwLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHIgZnJvbSAnQGhhdC1jb3JlL3JlbmRlcmVyJztcblxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaW5pdCgpIHtcbiAgICBhd2FpdCByLnNldCgndmlldycsIHt9KTtcbn1cblxuXG5leHBvcnQgZnVuY3Rpb24gdnQoKSB7XG4gICAgcmV0dXJuIFsnZGl2JyxcbiAgICAgICAgWydkaXYnLCB7XG4gICAgICAgICAgICBwcm9wczoge1xuICAgICAgICAgICAgICAgIHN0eWxlOiAnbWFyZ2luLWJvdHRvbTogMTBweCd9fSxcbiAgICAgICAgICAgICdUaGUgZm9sbG93aW5nIGV4YW1wbGUgZGVtb25zdHJhdGVzIHVzYWdlIG9mIHRoZSBBSU1NIHNlcnZlciBmb3Igc3RhdGUgZXN0aW1hdGlvbi4gQSBzaW1wbGUgcG93ZXIgZ3JpZCAnLFxuICAgICAgICAgICAgJ2Jhc2VkIG9uIElFRUUgMTQtYnVzIGNhc2Ugd2FzIG1vZGVsZWQgYW5kIHNpbXVsYXRlZC4gU2ltdWxhdGVkIG1lYXN1cmVtZW50cyBhcmUgcGFzc2VkIHRvIHRoZSBBSU1NICcsXG4gICAgICAgICAgICAnc2VydmVyLCB3aGljaCBnZW5lcmF0ZXMgZXN0aW1hdGlvbnMgZm9yIGV4aXN0aW5nIG1lYXN1cmVtZW50cyBhbmQgdGhlIG1pc3Npbmcgb25lcy4gTWVhc3VyZW1lbnRzIGFyZSAnLFxuICAgICAgICAgICAgJ3JlcHJlc2VudGVkIHdpdGggdGhlIGdyZWVuIGNvbG9yIGFuZCBlc3RpbWF0aW9ucyBhcmUgYmx1ZS4gSWYgYW4gZXN0aW1hdGlvbiBvciBhIG1lYXN1cmVtZW50IGlzIG5vdCAnLFxuICAgICAgICAgICAgJ2F2YWlsYWJsZSwgdGhhdCBpcyByZXByZXNlbnRlZCB3aXRoIHRoZSBncmV5IGNvbG9yLiBJZiB5b3UgYXJlIHNlZWluZyB0aGlzIGludGVyZmFjZSwgdGhhdCBtZWFucyB0aGF0ICcsXG4gICAgICAgICAgICAnaGF0IGlzIHNldCB1cCBhbmQgcnVubmluZy4gSWYgbWVhc3VyZW1lbnRzIChmaXJzdCB0d28gUCBhbmQgUSBsYWJlbHMgb2YgZXZlcnkgYnVzKSBhcmUgbWlzc2luZywgbWFrZSAnLFxuICAgICAgICAgICAgJ3N1cmUgdGhhdCB0aGUgc2ltdWxhdG9yIGlzIHN0YXJ0ZWQuIElmIGVzdGltYXRpb25zIGFyZSBtaXNzaW5nLCBtYWtlIHN1cmUgdGhhdCB0aGUgQUlNTSBzZXJ2ZXIgaXMgJyxcbiAgICAgICAgICAgICdzdGFydGVkLicsXG4gICAgICAgIF0sXG4gICAgICAgIFsnc3ZnJywge1xuICAgICAgICAgICAgYXR0cnM6IHsgdmlld0JveDogXCIwIC01IDE1MDAgNzAwXCIsIHN0cm9rZTogJ2JsYWNrJywgZmlsbDogJyMwMDAwMDAwMCd9fSxcblxuICAgICAgICAgICAgdGV4dCg1MCwgMjUsICdCVVMgMScpLFxuICAgICAgICAgICAgbWVhc3VyZW1lbnRzKDEwMCwgNSwgMCksXG4gICAgICAgICAgICBidXMoNTAsIDkwLCAxMDApLFxuICAgICAgICAgICAgZXh0KDUwLCA1MCksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzYwLCA3MF0sIFs2MCwgOTBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNjUsIDkwXSwgWzY1LCAyMjBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNjUsIDEwMF0sIFszMTUsIDEwMF0sIFszMTUsIDIyMF0pLFxuXG4gICAgICAgICAgICB0ZXh0KDc1LCAxODAsICdCVVMgNScpLFxuICAgICAgICAgICAgbWVhc3VyZW1lbnRzKDEzMCwgMTM1LCA0KSxcbiAgICAgICAgICAgIGJ1cyg1MCwgMjIwLCAxMDApLFxuICAgICAgICAgICAgbG9hZCg3NSwgMjMwKSxcbiAgICAgICAgICAgIHRyYWZvKDYwLCAyMjAsIDM1MCksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzg1LCAyMjBdLCBbODUsIDIzMF0pLFxuXG4gICAgICAgICAgICB0ZXh0KDMyNSwgMTgwLCAnQlVTIDInKSxcbiAgICAgICAgICAgIG1lYXN1cmVtZW50cygzODAsIDEzNSwgMSksXG4gICAgICAgICAgICBidXMoMzAwLCAyMjAsIDEwMCksXG4gICAgICAgICAgICBsb2FkKDMyNSwgMjMwKSxcbiAgICAgICAgICAgIGdlbmVyYXRvcigzNTAsIDIzMCksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzMxNSwgMjIwXSwgWzMxNSwgMzUwXSksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzM4NSwgMjIwXSwgWzM4NSwgMjI1XSwgWzUxNSwgMjI1XSwgWzUxNSwgMzUwXSksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzMzNSwgMjIwXSwgWzMzNSwgMjMwXSksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzM2MCwgMjIwXSwgWzM2MCwgMjMwXSksXG5cbiAgICAgICAgICAgIHRleHQoNzUsIDMxMCwgJ0JVUyA2JyksXG4gICAgICAgICAgICBtZWFzdXJlbWVudHMoMTMwLCAyNjUsIDUpLFxuICAgICAgICAgICAgYnVzKDUwLCAzNTAsIDEwMCksXG4gICAgICAgICAgICBsb2FkKDc1LCAzNjApLFxuICAgICAgICAgICAgZ2VuZXJhdG9yKDEwMCwgMzYwKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNjUsIDM1MF0sIFs2NSwgNTgwXSksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzY1LCA0ODBdLCBbMTE1LCA0ODBdLCBbMTE1LCA0OTBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNjUsIDUzMF0sIFszMzAsIDUzMF0sIFszMzAsIDU4MF0pLFxuICAgICAgICAgICAgcG93ZXJMaW5lKFs4NSwgMzUwXSwgWzg1LCAzNjBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbMTEwLCAzNTBdLCBbMTEwLCAzNjBdKSxcblxuICAgICAgICAgICAgdGV4dCgzMjUsIDMxMCwgJ0JVUyAzJyksXG4gICAgICAgICAgICBtZWFzdXJlbWVudHMoMzgwLCAyNjUsIDIpLFxuICAgICAgICAgICAgYnVzKDMwMCwgMzUwLCAxMDApLFxuICAgICAgICAgICAgbG9hZCgzMjUsIDM2MCksXG4gICAgICAgICAgICBnZW5lcmF0b3IoMzUwLCAzNjApLFxuICAgICAgICAgICAgcG93ZXJMaW5lKFszODUsIDM1MF0sIFszODUsIDM2MF0sIFs1MTAsIDM2MF0sIFs1MTAsIDM1MF0pLFxuICAgICAgICAgICAgcG93ZXJMaW5lKFszMzUsIDM1MF0sIFszMzUsIDM2MF0pLFxuICAgICAgICAgICAgcG93ZXJMaW5lKFszNjAsIDM1MF0sIFszNjAsIDM2MF0pLFxuXG4gICAgICAgICAgICB0ZXh0KDUyNSwgMzEwLCAnQlVTIDQnKSxcbiAgICAgICAgICAgIG1lYXN1cmVtZW50cyg1ODAsIDI2NSwgMyksXG4gICAgICAgICAgICBidXMoNTAwLCAzNTAsIDEwMCksXG4gICAgICAgICAgICBsb2FkKDUzMCwgMzYwKSxcbiAgICAgICAgICAgIHRyYWZvKDUxNSwgMzUwLCA0OTApLFxuICAgICAgICAgICAgcG93ZXJMaW5lKFs1ODUsIDM1MF0sIFs1ODUsIDM2MF0sIFs3MTAsIDM2MF0pLFxuICAgICAgICAgICAgdHJhZm8oNzEwLCAzNjAsIDQ5MCksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzU0MCwgMzUwXSwgWzU0MCwgMzYwXSksXG5cbiAgICAgICAgICAgIHRleHQoNzUsIDQ1MCwgJ0JVUyAxMScpLFxuICAgICAgICAgICAgbWVhc3VyZW1lbnRzKDEzMCwgNDA1LCAxMCksXG4gICAgICAgICAgICBidXMoMTAwLCA0OTAsIDEwMCksXG4gICAgICAgICAgICBsb2FkKDEyNSwgNTAwKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbMTg1LCA0OTBdLCBbMTg1LCA1MDBdLCBbMzE1LCA1MDBdLCBbMzE1LCA0OTBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbMTM1LCA0OTBdLCBbMTM1LCA1MDBdKSxcblxuICAgICAgICAgICAgdGV4dCgzMjUsIDQ1MCwgJ0JVUyAxMCcpLFxuICAgICAgICAgICAgbWVhc3VyZW1lbnRzKDM4MCwgNDA1LCA5KSxcbiAgICAgICAgICAgIGJ1cygzMDAsIDQ5MCwgMTAwKSxcbiAgICAgICAgICAgIGxvYWQoMzI1LCA1MDApLFxuICAgICAgICAgICAgcG93ZXJMaW5lKFszODUsIDQ5MF0sIFszODUsIDUwMF0sIFs1MTUsIDUwMF0sIFs1MTUsIDQ5MF0pLFxuICAgICAgICAgICAgcG93ZXJMaW5lKFszMzUsIDQ5MF0sIFszMzUsIDUwMF0pLFxuXG4gICAgICAgICAgICB0ZXh0KDUzNSwgNDUwLCAnQlVTIDknKSxcbiAgICAgICAgICAgIG1lYXN1cmVtZW50cyg1OTAsIDQwNSwgOCksXG4gICAgICAgICAgICBidXMoNTAwLCA0OTAsIDEwMCksXG4gICAgICAgICAgICBsb2FkKDUzMCwgNTAwKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNTg1LCA0OTBdLCBbNTg1LCA1MDBdKSxcbiAgICAgICAgICAgIHRyYWZvSG9yaXpvbnRhbCg1ODUsIDcxNSwgNTAwKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNzE1LCA1MDBdLCBbNzE1LCA0OTBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNTgwLCA0OTBdLCBbNTgwLCA1ODBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbNTQwLCA0OTBdLCBbNTQwLCA1MDBdKSxcblxuICAgICAgICAgICAgdGV4dCg3MzUsIDQ1MCwgJ0JVUyA3JyksXG4gICAgICAgICAgICBtZWFzdXJlbWVudHMoNzkwLCA0MDUsIDYpLFxuICAgICAgICAgICAgYnVzKDcwMCwgNDkwLCAxMDApLFxuICAgICAgICAgICAgdHJhZm8oNzIwLCA0OTAsIDU4MCksXG5cbiAgICAgICAgICAgIHRleHQoMjUsIDY0MCwgJ0JVUyAxMicpLFxuICAgICAgICAgICAgbWVhc3VyZW1lbnRzKDkwLCA2MDUsIDExKSxcbiAgICAgICAgICAgIGJ1cyg1MCwgNTgwLCAxMDApLFxuICAgICAgICAgICAgbG9hZCg4MCwgNTQwKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbMTM1LCA1ODBdLCBbMTM1LCA1OTBdLCBbMzE1LCA1OTBdLCBbMzE1LCA1ODBdKSxcbiAgICAgICAgICAgIHBvd2VyTGluZShbOTAsIDU4MF0sIFs5MCwgNTYwXSksXG5cbiAgICAgICAgICAgIHRleHQoMjgwLCA2NDAsICdCVVMgMTMnKSxcbiAgICAgICAgICAgIG1lYXN1cmVtZW50cygzNDAsIDYwNSwgMTIpLFxuICAgICAgICAgICAgYnVzKDMwMCwgNTgwLCAxMDApLFxuICAgICAgICAgICAgbG9hZCgzNDAsIDU0MCksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzM4NSwgNTgwXSwgWzM4NSwgNTkwXSwgWzUxNSwgNTkwXSwgWzUxNSwgNTgwXSksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzM1MCwgNTgwXSwgWzM1MCwgNTYwXSksXG5cbiAgICAgICAgICAgIHRleHQoNDgwLCA2NDAsICdCVVMgMTQnKSxcbiAgICAgICAgICAgIG1lYXN1cmVtZW50cyg1NDAsIDYwNSwgMTMpLFxuICAgICAgICAgICAgYnVzKDUwMCwgNTgwLCAxMDApLFxuICAgICAgICAgICAgbG9hZCg1MzAsIDU0MCksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzU0MCwgNTgwXSwgWzU0MCwgNTYwXSksXG5cbiAgICAgICAgICAgIHRleHQoNjgwLCA2NDAsICdCVVMgOCcpLFxuICAgICAgICAgICAgbWVhc3VyZW1lbnRzKDc0MCwgNjA1LCA3KSxcbiAgICAgICAgICAgIGJ1cyg3MDAsIDU4MCwgMTAwKSxcbiAgICAgICAgICAgIGdlbmVyYXRvcig3NTUsIDU0MCksXG4gICAgICAgICAgICBwb3dlckxpbmUoWzc2NSwgNTgwXSwgWzc2NSwgNTYwXSlcbiAgICAgICAgXVxuICAgIF07XG59XG5cblxuZnVuY3Rpb24gYnVzKHgxLCB5LCB3aWR0aCl7XG4gICAgcmV0dXJuIFsnbGluZScsIHtcbiAgICAgICAgYXR0cnM6IHt4MTogeDEsIHkxOiB5LCB4MjogeDEgKyB3aWR0aCwgeTI6IHksICdzdHJva2Utd2lkdGgnOiA0fX1dO1xufVxuXG5cbmZ1bmN0aW9uIHBvd2VyTGluZSguLi5wb2ludHMpe1xuICAgIGNvbnN0IHZ0cyA9IFtdO1xuICAgIGZvciAobGV0IGkgPSAxOyBpIDwgcG9pbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZ0cy5wdXNoKFsnbGluZScsIHtcbiAgICAgICAgICAgIGF0dHJzOiB7XG4gICAgICAgICAgICAgICAgeDE6IHBvaW50c1tpLTFdWzBdLFxuICAgICAgICAgICAgICAgIHkxOiBwb2ludHNbaS0xXVsxXSxcbiAgICAgICAgICAgICAgICB4MjogcG9pbnRzW2ldWzBdLFxuICAgICAgICAgICAgICAgIHkyOiBwb2ludHNbaV1bMV0sXG4gICAgICAgICAgICB9fV0pXG4gICAgfVxuICAgIHJldHVybiBbJ2cnLCB2dHNdO1xufVxuXG5cbmZ1bmN0aW9uIHRyYWZvKHgsIHkxLCB5Mikge1xuICAgIGNvbnN0IHIgPSAxMFxuXG4gICAgY29uc3QgaGVpZ2h0ID0gTWF0aC5hYnMoeTIgLSB5MSk7XG4gICAgY29uc3QgY3kxID0geTEgKyBoZWlnaHQgLyAyIC0gciAqIDEuNTtcbiAgICBjb25zdCBjeTIgPSB5MiAtIGhlaWdodCAvIDIgKyByICogMS41O1xuICAgIHJldHVybiBbJ2cnLFxuICAgICAgICBbJ2xpbmUnLCB7YXR0cnM6IHsgeDE6IHgsIHkxOiB5MSwgeDI6IHgsIHkyOiBjeTEgfX1dLFxuICAgICAgICBbJ2NpcmNsZScsIHthdHRyczoge2N4OiB4LCBjeTogY3kxICsgciwgcjogcn19XSxcbiAgICAgICAgWydjaXJjbGUnLCB7YXR0cnM6IHtjeDogeCwgY3k6IGN5MiAtIHIsIHI6IHJ9fV0sXG4gICAgICAgIFsnbGluZScsIHthdHRyczogeyB4MTogeCwgeTE6IGN5MiwgeDI6IHgsIHkyOiB5MiB9fV1cbiAgICBdO1xufVxuXG5cbmZ1bmN0aW9uIHRyYWZvSG9yaXpvbnRhbCh4MSwgeDIsIHkpIHtcbiAgICBjb25zdCByID0gMTBcblxuICAgIGNvbnN0IHdpZHRoID0gTWF0aC5hYnMoeDEgLSB4Mik7XG4gICAgY29uc3QgY3gxID0geDEgKyB3aWR0aCAvIDIgLSByICogMS41O1xuICAgIGNvbnN0IGN4MiA9IHgyIC0gd2lkdGggLyAyICsgciAqIDEuNTtcbiAgICByZXR1cm4gWydnJyxcbiAgICAgICAgWydsaW5lJywge2F0dHJzOiB7IHgxOiB4MSwgeTE6IHksIHgyOiBjeDEsIHkyOiB5IH19XSxcbiAgICAgICAgWydjaXJjbGUnLCB7YXR0cnM6IHtjeDogY3gxICsgciwgY3k6IHksIHI6IHJ9fV0sXG4gICAgICAgIFsnY2lyY2xlJywge2F0dHJzOiB7Y3g6IGN4MiAtIHIsIGN5OiB5LCByOiByfX1dLFxuICAgICAgICBbJ2xpbmUnLCB7YXR0cnM6IHsgeDE6IGN4MiwgeTE6IHksIHgyOiB4MiwgeTI6IHkgfX1dXG4gICAgXTtcbn1cblxuXG5mdW5jdGlvbiBnZW5lcmF0b3IoeCwgeSkge1xuICAgIHJldHVybiByZWN0TGV0dGVyKHgsIHksICdHJyk7XG59XG5cblxuZnVuY3Rpb24gbG9hZCh4LCB5KSB7XG4gICAgcmV0dXJuIHJlY3RMZXR0ZXIoeCwgeSwgJ0wnKTtcbn1cblxuXG5mdW5jdGlvbiBleHQoeCwgeSkge1xuICAgIHJldHVybiByZWN0TGV0dGVyKHgsIHksICdFJyk7XG59XG5cblxuZnVuY3Rpb24gcmVjdExldHRlcih4LCB5LCBsZXR0ZXIpIHtcbiAgICByZXR1cm4gWydnJyxcbiAgICAgICAgWydyZWN0Jywge2F0dHJzOiB7eDogeCwgeTogeSwgd2lkdGg6IDIwLCBoZWlnaHQ6IDIwfX1dLFxuICAgICAgICB0ZXh0KHggKyA1LCB5ICsgMTUsIGxldHRlcilcbiAgICBdO1xufVxuXG5cbmZ1bmN0aW9uIG1lYXN1cmVtZW50SXRlbSh4LCB5LCBwcmVmaXgsIHN1ZmZpeCwgYnVzSWQsIHR5cGUpIHtcbiAgICByZXR1cm4gcHJvY2Vzc0l0ZW0oeCwgeSwgcHJlZml4LCBzdWZmaXgsICdncmVlbicsIFsnbWVhc3VyZW1lbnQnLCBidXNJZCwgdHlwZV0pO1xufVxuXG5cbmZ1bmN0aW9uIGVzdGltYXRpb25JdGVtKHgsIHksIHByZWZpeCwgc3VmZml4LCBidXNJZCwgdHlwZSkge1xuICAgIHJldHVybiBwcm9jZXNzSXRlbSh4LCB5LCBwcmVmaXgsIHN1ZmZpeCwgJ2JsdWUnLCBbJ2VzdGltYXRpb24nLCBidXNJZCwgdHlwZV0pO1xufVxuXG5cbmZ1bmN0aW9uIHByb2Nlc3NJdGVtKHgsIHksIHByZWZpeCwgc3VmZml4LCBjb2xvciwgcGF0aCkge1xuICAgIGNvbnN0IHZhbHVlID0gci5nZXQoJ3JlbW90ZScsICdhZGFwdGVyJywgLi4ucGF0aCk7XG4gICAgcmV0dXJuIHRleHQoXG4gICAgICAgIHgsIHksIGAke3ByZWZpeH0gJHt2YWx1ZSAhPSBudWxsID8gTWF0aC5yb3VuZCh2YWx1ZSAqIDEwMCkgLyAxMDAgOiAnPz8/Pyd9ICR7c3VmZml4fWAsICdzbWFsbGVyJyxcbiAgICAgICAgdmFsdWUgIT0gbnVsbCA/IGNvbG9yIDogJ2dyYXknKTtcbn1cblxuXG5mdW5jdGlvbiBtZWFzdXJlbWVudHMoeCwgeSwgYnVzSWQpIHtcbiAgICByZXR1cm4gWydnJyxcbiAgICAgICAgbWVhc3VyZW1lbnRJdGVtKHgsIHksICdQOicsICdNVycsIGJ1c0lkLCAncCcpLFxuICAgICAgICBtZWFzdXJlbWVudEl0ZW0oeCwgeSArIDE1LCAnUTonLCAnbVZhcicsIGJ1c0lkLCAncScpLFxuICAgICAgICBlc3RpbWF0aW9uSXRlbSh4LCB5ICsgMzAsICdQOicsICdNVycsIGJ1c0lkLCAncCcpLFxuICAgICAgICBlc3RpbWF0aW9uSXRlbSh4LCB5ICsgNDUsICdROicsICdtVmFyJywgYnVzSWQsICdxJyksXG4gICAgICAgIGVzdGltYXRpb25JdGVtKHgsIHkgKyA2MCwgJ1Y6JywgJ3AudS4nLCBidXNJZCwgJ3YnKSxcbiAgICAgICAgZXN0aW1hdGlvbkl0ZW0oeCAtIDUsIHkgKyA3NSwgJ1ZhOicsICfCsCcsIGJ1c0lkLCAndmEnKSxcbiAgICBdXG59XG5cblxuZnVuY3Rpb24gdGV4dCh4LCB5LCB2YWx1ZSwgZm9udFNpemUgPSAnZGVmYXVsdCcsIHN0cm9rZSA9ICdibGFjaycpIHtcbiAgICByZXR1cm4gWyd0ZXh0Jywge2F0dHJzOiB7eDogeCwgeTogeSwgJ2ZvbnQtc2l6ZSc6IGZvbnRTaXplLCBzdHJva2U6IHN0cm9rZX19LCB2YWx1ZV1cbn1cblxuXG5leHBvcnQgZnVuY3Rpb24gZGVzdHJveSgpIHt9XG4iXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///0\n"); + +/***/ }), +/* 1 */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"Renderer\": () => (/* binding */ Renderer),\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var snabbdom_init__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);\n/* harmony import */ var snabbdom_h__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6);\n/* harmony import */ var snabbdom_modules_class__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);\n/* harmony import */ var snabbdom_modules_dataset__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);\n/* harmony import */ var snabbdom_modules_eventlisteners__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);\n/* harmony import */ var snabbdom_modules_style__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(10);\n/* harmony import */ var _hat_core_util__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(11);\n/** @module @hat-core/renderer\n */\n\n\n\n\n\n\n\n\n\n\n\n// patched version of snabbdom's es/modules/attributes.js\nconst snabbdomAttributes = (() => {\n function updateAttrs(oldVnode, vnode) {\n var key, elm = vnode.elm, oldAttrs = oldVnode.data.attrs, attrs = vnode.data.attrs;\n if (!oldAttrs && !attrs)\n return;\n if (oldAttrs === attrs)\n return;\n oldAttrs = oldAttrs || {};\n attrs = attrs || {};\n for (key in attrs) {\n var cur = attrs[key];\n var old = oldAttrs[key];\n if (old !== cur) {\n if (cur === true) {\n elm.setAttribute(key, \"\");\n }\n else if (cur === false) {\n elm.removeAttribute(key);\n }\n else {\n elm.setAttribute(key, cur);\n }\n }\n }\n for (key in oldAttrs) {\n if (!(key in attrs)) {\n elm.removeAttribute(key);\n }\n }\n }\n return { create: updateAttrs, update: updateAttrs };\n})();\n\n\n// patched version of snabbdom's es/modules/props.js\nconst snabbdomProps = (() => {\n function updateProps(oldVnode, vnode) {\n var key, cur, old, elm = vnode.elm, oldProps = oldVnode.data.props, props = vnode.data.props;\n if (!oldProps && !props)\n return;\n if (oldProps === props)\n return;\n oldProps = oldProps || {};\n props = props || {};\n for (key in oldProps) {\n if (!props[key]) {\n if (key === 'style') {\n elm[key] = '';\n } else {\n delete elm[key];\n }\n }\n }\n for (key in props) {\n cur = props[key];\n old = oldProps[key];\n if (old !== cur && (key !== 'value' || elm[key] !== cur)) {\n elm[key] = cur;\n }\n }\n }\n return { create: updateProps, update: updateProps };\n})();\n\n\nconst patch = (0,snabbdom_init__WEBPACK_IMPORTED_MODULE_0__.init)([\n snabbdomAttributes,\n snabbdom_modules_class__WEBPACK_IMPORTED_MODULE_2__.classModule,\n snabbdomProps,\n snabbdom_modules_dataset__WEBPACK_IMPORTED_MODULE_3__.datasetModule,\n snabbdom_modules_eventlisteners__WEBPACK_IMPORTED_MODULE_4__.eventListenersModule\n]);\n\n\nfunction vhFromArray(node) {\n if (!node)\n return [];\n if (_hat_core_util__WEBPACK_IMPORTED_MODULE_6__.isString(node))\n return node;\n if (!_hat_core_util__WEBPACK_IMPORTED_MODULE_6__.isArray(node))\n throw 'Invalid node structure';\n if (node.length < 1)\n return [];\n if (typeof node[0] != 'string')\n return node.map(vhFromArray);\n const hasData = node.length > 1 && _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.isObject(node[1]);\n const children = _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.pipe(\n _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.map(vhFromArray),\n _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.flatten,\n Array.from\n )(node.slice(hasData ? 2 : 1));\n const result = hasData ?\n (0,snabbdom_h__WEBPACK_IMPORTED_MODULE_1__.h)(node[0], node[1], children) :\n (0,snabbdom_h__WEBPACK_IMPORTED_MODULE_1__.h)(node[0], children);\n return result;\n}\n\n/**\n * Virtual DOM renderer\n */\nclass Renderer extends EventTarget {\n\n /**\n * Calls `init` method\n * @param {HTMLElement} [el=document.body]\n * @param {Any} [initState=null]\n * @param {Function} [vtCb=null]\n * @param {Number} [maxFps=30]\n */\n constructor(el, initState, vtCb, maxFps) {\n super();\n this.init(el, initState, vtCb, maxFps);\n }\n\n /**\n * Initialize renderer\n * @param {HTMLElement} [el=document.body]\n * @param {Any} [initState=null]\n * @param {Function} [vtCb=null]\n * @param {Number} [maxFps=30]\n * @return {Promise}\n */\n init(el, initState, vtCb, maxFps) {\n this._state = null;\n this._changes = [];\n this._promise = null;\n this._timeout = null;\n this._lastRender = null;\n this._vtCb = vtCb;\n this._maxFps = _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.isNumber(maxFps) ? maxFps : 30;\n this._vNode = el || document.querySelector('body');\n if (initState)\n return this.change(_ => initState);\n return new Promise(resolve => { resolve(); });\n }\n\n /**\n * Render\n */\n render() {\n if (!this._vtCb)\n return;\n this._lastRender = performance.now();\n const vNode = vhFromArray(this._vtCb(this));\n patch(this._vNode, vNode);\n this._vNode = vNode;\n this.dispatchEvent(new CustomEvent('render', {detail: this._state}));\n }\n\n /**\n * Get current state value referenced by `paths`\n * @param {...Path} paths\n * @return {Any}\n */\n get(...paths) {\n return _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.get(paths, this._state);\n }\n\n /**\n * Change current state value referenced by `path`\n * @param {Path} path\n * @param {Any} value\n * @return {Promise}\n */\n set(path, value) {\n if (arguments.length < 2) {\n value = path;\n path = [];\n }\n return this.change(path, _ => value);\n }\n\n /**\n * Change current state value referenced by `path`\n * @param {Path} path\n * @param {Function} cb\n * @return {Promise}\n */\n change(path, cb) {\n if (arguments.length < 2) {\n cb = path;\n path = [];\n }\n this._changes.push([path, cb]);\n if (this._promise)\n return this._promise;\n this._promise = new Promise((resolve, reject) => {\n setTimeout(() => {\n try {\n this._change();\n } catch(e) {\n this._promise = null;\n reject(e);\n throw e;\n }\n this._promise = null;\n resolve();\n }, 0);\n });\n return this._promise;\n }\n\n _change() {\n let change = false;\n while (this._changes.length > 0) {\n const [path, cb] = this._changes.shift();\n const view = _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.get(path);\n const oldState = this._state;\n this._state = _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.change(path, cb, this._state);\n if (this._state && _hat_core_util__WEBPACK_IMPORTED_MODULE_6__.equals(view(oldState),\n view(this._state)))\n continue;\n change = true;\n if (!this._vtCb || this._timeout)\n continue;\n const delay = (!this._lastRender || !this._maxFps ?\n 0 :\n (1000 / this._maxFps) -\n (performance.now() - this._lastRender));\n this._timeout = setTimeout(() => {\n this._timeout = null;\n this.render();\n }, (delay > 0 ? delay : 0));\n }\n if (change)\n this.dispatchEvent(\n new CustomEvent('change', {detail: this._state}));\n }\n}\n// Renderer.prototype.set = u.curry(Renderer.prototype.set);\n// Renderer.prototype.change = u.curry(Renderer.prototype.change);\n\n\n/**\n * Default renderer\n * @static\n * @type {Renderer}\n */\nconst defaultRenderer = (() => {\n const r = (window && window.__hat_default_renderer) || new Renderer();\n if (window)\n window.__hat_default_renderer = r;\n return r;\n})();\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (defaultRenderer);\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvQGhhdC1jb3JlL3JlbmRlcmVyL2luZGV4LmpzPzM1ZjYiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTs7QUFFbUQ7QUFDVDtBQUMwQjtBQUNNO0FBQ1k7QUFDbEI7O0FBRWhDOzs7QUFHcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1osQ0FBQzs7O0FBR0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlCQUFpQjtBQUNqQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1osQ0FBQzs7O0FBR0QsY0FBYyxtREFBWTtBQUMxQjtBQUNBLElBQUksK0RBQWE7QUFDakI7QUFDQSxJQUFJLG1FQUFlO0FBQ25CLElBQUksaUZBQWE7QUFDakI7OztBQUdBO0FBQ0E7QUFDQTtBQUNBLFFBQVEsb0RBQVU7QUFDbEI7QUFDQSxTQUFTLG1EQUFTO0FBQ2xCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBdUMsb0RBQVU7QUFDakQscUJBQXFCLGdEQUFNO0FBQzNCLFFBQVEsK0NBQUs7QUFDYixRQUFRLG1EQUFTO0FBQ2pCO0FBQ0E7QUFDQTtBQUNBLFFBQVEsNkNBQVM7QUFDakIsUUFBUSw2Q0FBUztBQUNqQjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNPOztBQUVQO0FBQ0E7QUFDQSxlQUFlLFlBQVk7QUFDM0IsZUFBZSxJQUFJO0FBQ25CLGVBQWUsU0FBUztBQUN4QixlQUFlLE9BQU87QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsZUFBZSxZQUFZO0FBQzNCLGVBQWUsSUFBSTtBQUNuQixlQUFlLFNBQVM7QUFDeEIsZUFBZSxPQUFPO0FBQ3RCLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUJBQXVCLG9EQUFVO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBLHVDQUF1QyxXQUFXLEVBQUU7QUFDcEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzREFBc0Qsb0JBQW9CO0FBQzFFOztBQUVBO0FBQ0E7QUFDQSxlQUFlLFFBQVE7QUFDdkIsZ0JBQWdCO0FBQ2hCO0FBQ0E7QUFDQSxlQUFlLCtDQUFLO0FBQ3BCOztBQUVBO0FBQ0E7QUFDQSxlQUFlLEtBQUs7QUFDcEIsZUFBZSxJQUFJO0FBQ25CLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxlQUFlLEtBQUs7QUFDcEIsZUFBZSxTQUFTO0FBQ3hCLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlCQUFpQjtBQUNqQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2IsU0FBUztBQUNUO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5QkFBeUIsK0NBQUs7QUFDOUI7QUFDQSwwQkFBMEIsa0RBQVE7QUFDbEMsK0JBQStCLGtEQUFRO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQSwyQ0FBMkMsb0JBQW9CO0FBQy9EO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNELGlFQUFlLGVBQWUsRUFBQyIsImZpbGUiOiIxLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLyoqIEBtb2R1bGUgQGhhdC1jb3JlL3JlbmRlcmVyXG4gKi9cblxuaW1wb3J0IHtpbml0IGFzIHNuYWJiZG9tSW5pdH0gZnJvbSAnc25hYmJkb20vaW5pdCc7XG5pbXBvcnQge2ggYXMgc25hYmJkb21IfSBmcm9tICdzbmFiYmRvbS9oJztcbmltcG9ydCB7Y2xhc3NNb2R1bGUgYXMgc25hYmJkb21DbGFzc30gZnJvbSAnc25hYmJkb20vbW9kdWxlcy9jbGFzcyc7XG5pbXBvcnQge2RhdGFzZXRNb2R1bGUgYXMgc25hYmJkb21EYXRhc2V0fSBmcm9tICdzbmFiYmRvbS9tb2R1bGVzL2RhdGFzZXQnO1xuaW1wb3J0IHtldmVudExpc3RlbmVyc01vZHVsZSBhcyBzbmFiYmRvbUV2ZW50fSBmcm9tICdzbmFiYmRvbS9tb2R1bGVzL2V2ZW50bGlzdGVuZXJzJztcbmltcG9ydCB7c3R5bGVNb2R1bGUgYXMgc25hYmJkb21TdHlsZX0gZnJvbSAnc25hYmJkb20vbW9kdWxlcy9zdHlsZSc7XG5cbmltcG9ydCAqIGFzIHUgZnJvbSAnQGhhdC1jb3JlL3V0aWwnO1xuXG5cbi8vIHBhdGNoZWQgdmVyc2lvbiBvZiBzbmFiYmRvbSdzIGVzL21vZHVsZXMvYXR0cmlidXRlcy5qc1xuY29uc3Qgc25hYmJkb21BdHRyaWJ1dGVzID0gKCgpID0+IHtcbiAgICBmdW5jdGlvbiB1cGRhdGVBdHRycyhvbGRWbm9kZSwgdm5vZGUpIHtcbiAgICAgICAgdmFyIGtleSwgZWxtID0gdm5vZGUuZWxtLCBvbGRBdHRycyA9IG9sZFZub2RlLmRhdGEuYXR0cnMsIGF0dHJzID0gdm5vZGUuZGF0YS5hdHRycztcbiAgICAgICAgaWYgKCFvbGRBdHRycyAmJiAhYXR0cnMpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIGlmIChvbGRBdHRycyA9PT0gYXR0cnMpXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIG9sZEF0dHJzID0gb2xkQXR0cnMgfHwge307XG4gICAgICAgIGF0dHJzID0gYXR0cnMgfHwge307XG4gICAgICAgIGZvciAoa2V5IGluIGF0dHJzKSB7XG4gICAgICAgICAgICB2YXIgY3VyID0gYXR0cnNba2V5XTtcbiAgICAgICAgICAgIHZhciBvbGQgPSBvbGRBdHRyc1trZXldO1xuICAgICAgICAgICAgaWYgKG9sZCAhPT0gY3VyKSB7XG4gICAgICAgICAgICAgICAgaWYgKGN1ciA9PT0gdHJ1ZSkge1xuICAgICAgICAgICAgICAgICAgICBlbG0uc2V0QXR0cmlidXRlKGtleSwgXCJcIik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKGN1ciA9PT0gZmFsc2UpIHtcbiAgICAgICAgICAgICAgICAgICAgZWxtLnJlbW92ZUF0dHJpYnV0ZShrZXkpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgZWxtLnNldEF0dHJpYnV0ZShrZXksIGN1cik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGZvciAoa2V5IGluIG9sZEF0dHJzKSB7XG4gICAgICAgICAgICBpZiAoIShrZXkgaW4gYXR0cnMpKSB7XG4gICAgICAgICAgICAgICAgZWxtLnJlbW92ZUF0dHJpYnV0ZShrZXkpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiB7IGNyZWF0ZTogdXBkYXRlQXR0cnMsIHVwZGF0ZTogdXBkYXRlQXR0cnMgfTtcbn0pKCk7XG5cblxuLy8gcGF0Y2hlZCB2ZXJzaW9uIG9mIHNuYWJiZG9tJ3MgZXMvbW9kdWxlcy9wcm9wcy5qc1xuY29uc3Qgc25hYmJkb21Qcm9wcyA9ICgoKSA9PiB7XG4gICAgZnVuY3Rpb24gdXBkYXRlUHJvcHMob2xkVm5vZGUsIHZub2RlKSB7XG4gICAgICAgIHZhciBrZXksIGN1ciwgb2xkLCBlbG0gPSB2bm9kZS5lbG0sIG9sZFByb3BzID0gb2xkVm5vZGUuZGF0YS5wcm9wcywgcHJvcHMgPSB2bm9kZS5kYXRhLnByb3BzO1xuICAgICAgICBpZiAoIW9sZFByb3BzICYmICFwcm9wcylcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgaWYgKG9sZFByb3BzID09PSBwcm9wcylcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgb2xkUHJvcHMgPSBvbGRQcm9wcyB8fCB7fTtcbiAgICAgICAgcHJvcHMgPSBwcm9wcyB8fCB7fTtcbiAgICAgICAgZm9yIChrZXkgaW4gb2xkUHJvcHMpIHtcbiAgICAgICAgICAgIGlmICghcHJvcHNba2V5XSkge1xuICAgICAgICAgICAgICAgIGlmIChrZXkgPT09ICdzdHlsZScpIHtcbiAgICAgICAgICAgICAgICAgICAgZWxtW2tleV0gPSAnJztcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgZWxtW2tleV07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGZvciAoa2V5IGluIHByb3BzKSB7XG4gICAgICAgICAgICBjdXIgPSBwcm9wc1trZXldO1xuICAgICAgICAgICAgb2xkID0gb2xkUHJvcHNba2V5XTtcbiAgICAgICAgICAgIGlmIChvbGQgIT09IGN1ciAmJiAoa2V5ICE9PSAndmFsdWUnIHx8IGVsbVtrZXldICE9PSBjdXIpKSB7XG4gICAgICAgICAgICAgICAgZWxtW2tleV0gPSBjdXI7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHsgY3JlYXRlOiB1cGRhdGVQcm9wcywgdXBkYXRlOiB1cGRhdGVQcm9wcyB9O1xufSkoKTtcblxuXG5jb25zdCBwYXRjaCA9IHNuYWJiZG9tSW5pdChbXG4gICAgc25hYmJkb21BdHRyaWJ1dGVzLFxuICAgIHNuYWJiZG9tQ2xhc3MsXG4gICAgc25hYmJkb21Qcm9wcyxcbiAgICBzbmFiYmRvbURhdGFzZXQsXG4gICAgc25hYmJkb21FdmVudFxuXSk7XG5cblxuZnVuY3Rpb24gdmhGcm9tQXJyYXkobm9kZSkge1xuICAgIGlmICghbm9kZSlcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgIGlmICh1LmlzU3RyaW5nKG5vZGUpKVxuICAgICAgICByZXR1cm4gbm9kZTtcbiAgICBpZiAoIXUuaXNBcnJheShub2RlKSlcbiAgICAgICAgdGhyb3cgJ0ludmFsaWQgbm9kZSBzdHJ1Y3R1cmUnO1xuICAgIGlmIChub2RlLmxlbmd0aCA8IDEpXG4gICAgICAgIHJldHVybiBbXTtcbiAgICBpZiAodHlwZW9mIG5vZGVbMF0gIT0gJ3N0cmluZycpXG4gICAgICAgIHJldHVybiBub2RlLm1hcCh2aEZyb21BcnJheSk7XG4gICAgY29uc3QgaGFzRGF0YSA9IG5vZGUubGVuZ3RoID4gMSAmJiB1LmlzT2JqZWN0KG5vZGVbMV0pO1xuICAgIGNvbnN0IGNoaWxkcmVuID0gdS5waXBlKFxuICAgICAgICB1Lm1hcCh2aEZyb21BcnJheSksXG4gICAgICAgIHUuZmxhdHRlbixcbiAgICAgICAgQXJyYXkuZnJvbVxuICAgICkobm9kZS5zbGljZShoYXNEYXRhID8gMiA6IDEpKTtcbiAgICBjb25zdCByZXN1bHQgPSBoYXNEYXRhID9cbiAgICAgICAgc25hYmJkb21IKG5vZGVbMF0sIG5vZGVbMV0sIGNoaWxkcmVuKSA6XG4gICAgICAgIHNuYWJiZG9tSChub2RlWzBdLCBjaGlsZHJlbik7XG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuLyoqXG4gKiBWaXJ0dWFsIERPTSByZW5kZXJlclxuICovXG5leHBvcnQgY2xhc3MgUmVuZGVyZXIgZXh0ZW5kcyBFdmVudFRhcmdldCB7XG5cbiAgICAvKipcbiAgICAgKiBDYWxscyBgaW5pdGAgbWV0aG9kXG4gICAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gW2VsPWRvY3VtZW50LmJvZHldXG4gICAgICogQHBhcmFtIHtBbnl9IFtpbml0U3RhdGU9bnVsbF1cbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBbdnRDYj1udWxsXVxuICAgICAqIEBwYXJhbSB7TnVtYmVyfSBbbWF4RnBzPTMwXVxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKGVsLCBpbml0U3RhdGUsIHZ0Q2IsIG1heEZwcykge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLmluaXQoZWwsIGluaXRTdGF0ZSwgdnRDYiwgbWF4RnBzKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBJbml0aWFsaXplIHJlbmRlcmVyXG4gICAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gW2VsPWRvY3VtZW50LmJvZHldXG4gICAgICogQHBhcmFtIHtBbnl9IFtpbml0U3RhdGU9bnVsbF1cbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBbdnRDYj1udWxsXVxuICAgICAqIEBwYXJhbSB7TnVtYmVyfSBbbWF4RnBzPTMwXVxuICAgICAqIEByZXR1cm4ge1Byb21pc2V9XG4gICAgICovXG4gICAgaW5pdChlbCwgaW5pdFN0YXRlLCB2dENiLCBtYXhGcHMpIHtcbiAgICAgICAgdGhpcy5fc3RhdGUgPSBudWxsO1xuICAgICAgICB0aGlzLl9jaGFuZ2VzID0gW107XG4gICAgICAgIHRoaXMuX3Byb21pc2UgPSBudWxsO1xuICAgICAgICB0aGlzLl90aW1lb3V0ID0gbnVsbDtcbiAgICAgICAgdGhpcy5fbGFzdFJlbmRlciA9IG51bGw7XG4gICAgICAgIHRoaXMuX3Z0Q2IgPSB2dENiO1xuICAgICAgICB0aGlzLl9tYXhGcHMgPSB1LmlzTnVtYmVyKG1heEZwcykgPyBtYXhGcHMgOiAzMDtcbiAgICAgICAgdGhpcy5fdk5vZGUgPSBlbCB8fCBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdib2R5Jyk7XG4gICAgICAgIGlmIChpbml0U3RhdGUpXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5jaGFuZ2UoXyA9PiBpbml0U3RhdGUpO1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UocmVzb2x2ZSA9PiB7IHJlc29sdmUoKTsgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICAqIFJlbmRlclxuICAgICAgKi9cbiAgICByZW5kZXIoKSB7XG4gICAgICAgIGlmICghdGhpcy5fdnRDYilcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgdGhpcy5fbGFzdFJlbmRlciA9IHBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgICBjb25zdCB2Tm9kZSA9IHZoRnJvbUFycmF5KHRoaXMuX3Z0Q2IodGhpcykpO1xuICAgICAgICBwYXRjaCh0aGlzLl92Tm9kZSwgdk5vZGUpO1xuICAgICAgICB0aGlzLl92Tm9kZSA9IHZOb2RlO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEN1c3RvbUV2ZW50KCdyZW5kZXInLCB7ZGV0YWlsOiB0aGlzLl9zdGF0ZX0pKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXQgY3VycmVudCBzdGF0ZSB2YWx1ZSByZWZlcmVuY2VkIGJ5IGBwYXRoc2BcbiAgICAgKiBAcGFyYW0gey4uLlBhdGh9IHBhdGhzXG4gICAgICogQHJldHVybiB7QW55fVxuICAgICAqL1xuICAgIGdldCguLi5wYXRocykge1xuICAgICAgICByZXR1cm4gdS5nZXQocGF0aHMsIHRoaXMuX3N0YXRlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDaGFuZ2UgY3VycmVudCBzdGF0ZSB2YWx1ZSByZWZlcmVuY2VkIGJ5IGBwYXRoYFxuICAgICAqIEBwYXJhbSB7UGF0aH0gcGF0aFxuICAgICAqIEBwYXJhbSB7QW55fSB2YWx1ZVxuICAgICAqIEByZXR1cm4ge1Byb21pc2V9XG4gICAgICovXG4gICAgc2V0KHBhdGgsIHZhbHVlKSB7XG4gICAgICAgIGlmIChhcmd1bWVudHMubGVuZ3RoIDwgMikge1xuICAgICAgICAgICAgdmFsdWUgPSBwYXRoO1xuICAgICAgICAgICAgcGF0aCA9IFtdO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmNoYW5nZShwYXRoLCBfID0+IHZhbHVlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDaGFuZ2UgY3VycmVudCBzdGF0ZSB2YWx1ZSByZWZlcmVuY2VkIGJ5IGBwYXRoYFxuICAgICAqIEBwYXJhbSB7UGF0aH0gcGF0aFxuICAgICAqIEBwYXJhbSB7RnVuY3Rpb259IGNiXG4gICAgICogQHJldHVybiB7UHJvbWlzZX1cbiAgICAgKi9cbiAgICBjaGFuZ2UocGF0aCwgY2IpIHtcbiAgICAgICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPCAyKSB7XG4gICAgICAgICAgICBjYiA9IHBhdGg7XG4gICAgICAgICAgICBwYXRoID0gW107XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5fY2hhbmdlcy5wdXNoKFtwYXRoLCBjYl0pO1xuICAgICAgICBpZiAodGhpcy5fcHJvbWlzZSlcbiAgICAgICAgICAgIHJldHVybiB0aGlzLl9wcm9taXNlO1xuICAgICAgICB0aGlzLl9wcm9taXNlID0gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5fY2hhbmdlKCk7XG4gICAgICAgICAgICAgICAgfSBjYXRjaChlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX3Byb21pc2UgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICByZWplY3QoZSk7XG4gICAgICAgICAgICAgICAgICAgIHRocm93IGU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHRoaXMuX3Byb21pc2UgPSBudWxsO1xuICAgICAgICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgICAgIH0sIDApO1xuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3Byb21pc2U7XG4gICAgfVxuXG4gICAgX2NoYW5nZSgpIHtcbiAgICAgICAgbGV0IGNoYW5nZSA9IGZhbHNlO1xuICAgICAgICB3aGlsZSAodGhpcy5fY2hhbmdlcy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBjb25zdCBbcGF0aCwgY2JdID0gdGhpcy5fY2hhbmdlcy5zaGlmdCgpO1xuICAgICAgICAgICAgY29uc3QgdmlldyA9IHUuZ2V0KHBhdGgpO1xuICAgICAgICAgICAgY29uc3Qgb2xkU3RhdGUgPSB0aGlzLl9zdGF0ZTtcbiAgICAgICAgICAgIHRoaXMuX3N0YXRlID0gdS5jaGFuZ2UocGF0aCwgY2IsIHRoaXMuX3N0YXRlKTtcbiAgICAgICAgICAgIGlmICh0aGlzLl9zdGF0ZSAmJiB1LmVxdWFscyh2aWV3KG9sZFN0YXRlKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2aWV3KHRoaXMuX3N0YXRlKSkpXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICBjaGFuZ2UgPSB0cnVlO1xuICAgICAgICAgICAgaWYgKCF0aGlzLl92dENiIHx8IHRoaXMuX3RpbWVvdXQpXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICBjb25zdCBkZWxheSA9ICghdGhpcy5fbGFzdFJlbmRlciB8fCAhdGhpcy5fbWF4RnBzID9cbiAgICAgICAgICAgICAgICAwIDpcbiAgICAgICAgICAgICAgICAoMTAwMCAvIHRoaXMuX21heEZwcykgLVxuICAgICAgICAgICAgICAgIChwZXJmb3JtYW5jZS5ub3coKSAtIHRoaXMuX2xhc3RSZW5kZXIpKTtcbiAgICAgICAgICAgIHRoaXMuX3RpbWVvdXQgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLl90aW1lb3V0ID0gbnVsbDtcbiAgICAgICAgICAgICAgICB0aGlzLnJlbmRlcigpO1xuICAgICAgICAgICAgfSwgKGRlbGF5ID4gMCA/IGRlbGF5IDogMCkpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjaGFuZ2UpXG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQoXG4gICAgICAgICAgICAgICAgbmV3IEN1c3RvbUV2ZW50KCdjaGFuZ2UnLCB7ZGV0YWlsOiB0aGlzLl9zdGF0ZX0pKTtcbiAgICB9XG59XG4vLyBSZW5kZXJlci5wcm90b3R5cGUuc2V0ID0gdS5jdXJyeShSZW5kZXJlci5wcm90b3R5cGUuc2V0KTtcbi8vIFJlbmRlcmVyLnByb3RvdHlwZS5jaGFuZ2UgPSB1LmN1cnJ5KFJlbmRlcmVyLnByb3RvdHlwZS5jaGFuZ2UpO1xuXG5cbi8qKlxuICogRGVmYXVsdCByZW5kZXJlclxuICogQHN0YXRpY1xuICogQHR5cGUge1JlbmRlcmVyfVxuICovXG5jb25zdCBkZWZhdWx0UmVuZGVyZXIgPSAoKCkgPT4ge1xuICAgIGNvbnN0IHIgPSAod2luZG93ICYmIHdpbmRvdy5fX2hhdF9kZWZhdWx0X3JlbmRlcmVyKSB8fCBuZXcgUmVuZGVyZXIoKTtcbiAgICBpZiAod2luZG93KVxuICAgICAgICB3aW5kb3cuX19oYXRfZGVmYXVsdF9yZW5kZXJlciA9IHI7XG4gICAgcmV0dXJuIHI7XG59KSgpO1xuZXhwb3J0IGRlZmF1bHQgZGVmYXVsdFJlbmRlcmVyO1xuIl0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///1\n"); + +/***/ }), +/* 2 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"init\": () => (/* binding */ init)\n/* harmony export */ });\n/* harmony import */ var _vnode_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);\n/* harmony import */ var _is_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);\n/* harmony import */ var _htmldomapi_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);\n\n\n\nfunction isUndef(s) {\n return s === undefined;\n}\nfunction isDef(s) {\n return s !== undefined;\n}\nconst emptyNode = (0,_vnode_js__WEBPACK_IMPORTED_MODULE_0__.vnode)('', {}, [], undefined, undefined);\nfunction sameVnode(vnode1, vnode2) {\n return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;\n}\nfunction isVnode(vnode) {\n return vnode.sel !== undefined;\n}\nfunction createKeyToOldIdx(children, beginIdx, endIdx) {\n var _a;\n const map = {};\n for (let i = beginIdx; i <= endIdx; ++i) {\n const key = (_a = children[i]) === null || _a === void 0 ? void 0 : _a.key;\n if (key !== undefined) {\n map[key] = i;\n }\n }\n return map;\n}\nconst hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post'];\nfunction init(modules, domApi) {\n let i;\n let j;\n const cbs = {\n create: [],\n update: [],\n remove: [],\n destroy: [],\n pre: [],\n post: []\n };\n const api = domApi !== undefined ? domApi : _htmldomapi_js__WEBPACK_IMPORTED_MODULE_2__.htmlDomApi;\n for (i = 0; i < hooks.length; ++i) {\n cbs[hooks[i]] = [];\n for (j = 0; j < modules.length; ++j) {\n const hook = modules[j][hooks[i]];\n if (hook !== undefined) {\n cbs[hooks[i]].push(hook);\n }\n }\n }\n function emptyNodeAt(elm) {\n const id = elm.id ? '#' + elm.id : '';\n const c = elm.className ? '.' + elm.className.split(' ').join('.') : '';\n return (0,_vnode_js__WEBPACK_IMPORTED_MODULE_0__.vnode)(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);\n }\n function createRmCb(childElm, listeners) {\n return function rmCb() {\n if (--listeners === 0) {\n const parent = api.parentNode(childElm);\n api.removeChild(parent, childElm);\n }\n };\n }\n function createElm(vnode, insertedVnodeQueue) {\n var _a, _b;\n let i;\n let data = vnode.data;\n if (data !== undefined) {\n const init = (_a = data.hook) === null || _a === void 0 ? void 0 : _a.init;\n if (isDef(init)) {\n init(vnode);\n data = vnode.data;\n }\n }\n const children = vnode.children;\n const sel = vnode.sel;\n if (sel === '!') {\n if (isUndef(vnode.text)) {\n vnode.text = '';\n }\n vnode.elm = api.createComment(vnode.text);\n }\n else if (sel !== undefined) {\n // Parse selector\n const hashIdx = sel.indexOf('#');\n const dotIdx = sel.indexOf('.', hashIdx);\n const hash = hashIdx > 0 ? hashIdx : sel.length;\n const dot = dotIdx > 0 ? dotIdx : sel.length;\n const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;\n const elm = vnode.elm = isDef(data) && isDef(i = data.ns)\n ? api.createElementNS(i, tag)\n : api.createElement(tag);\n if (hash < dot)\n elm.setAttribute('id', sel.slice(hash + 1, dot));\n if (dotIdx > 0)\n elm.setAttribute('class', sel.slice(dot + 1).replace(/\\./g, ' '));\n for (i = 0; i < cbs.create.length; ++i)\n cbs.create[i](emptyNode, vnode);\n if (_is_js__WEBPACK_IMPORTED_MODULE_1__.array(children)) {\n for (i = 0; i < children.length; ++i) {\n const ch = children[i];\n if (ch != null) {\n api.appendChild(elm, createElm(ch, insertedVnodeQueue));\n }\n }\n }\n else if (_is_js__WEBPACK_IMPORTED_MODULE_1__.primitive(vnode.text)) {\n api.appendChild(elm, api.createTextNode(vnode.text));\n }\n const hook = vnode.data.hook;\n if (isDef(hook)) {\n (_b = hook.create) === null || _b === void 0 ? void 0 : _b.call(hook, emptyNode, vnode);\n if (hook.insert) {\n insertedVnodeQueue.push(vnode);\n }\n }\n }\n else {\n vnode.elm = api.createTextNode(vnode.text);\n }\n return vnode.elm;\n }\n function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {\n for (; startIdx <= endIdx; ++startIdx) {\n const ch = vnodes[startIdx];\n if (ch != null) {\n api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);\n }\n }\n }\n function invokeDestroyHook(vnode) {\n var _a, _b;\n const data = vnode.data;\n if (data !== undefined) {\n (_b = (_a = data === null || data === void 0 ? void 0 : data.hook) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a, vnode);\n for (let i = 0; i < cbs.destroy.length; ++i)\n cbs.destroy[i](vnode);\n if (vnode.children !== undefined) {\n for (let j = 0; j < vnode.children.length; ++j) {\n const child = vnode.children[j];\n if (child != null && typeof child !== 'string') {\n invokeDestroyHook(child);\n }\n }\n }\n }\n }\n function removeVnodes(parentElm, vnodes, startIdx, endIdx) {\n var _a, _b;\n for (; startIdx <= endIdx; ++startIdx) {\n let listeners;\n let rm;\n const ch = vnodes[startIdx];\n if (ch != null) {\n if (isDef(ch.sel)) {\n invokeDestroyHook(ch);\n listeners = cbs.remove.length + 1;\n rm = createRmCb(ch.elm, listeners);\n for (let i = 0; i < cbs.remove.length; ++i)\n cbs.remove[i](ch, rm);\n const removeHook = (_b = (_a = ch === null || ch === void 0 ? void 0 : ch.data) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.remove;\n if (isDef(removeHook)) {\n removeHook(ch, rm);\n }\n else {\n rm();\n }\n }\n else { // Text node\n api.removeChild(parentElm, ch.elm);\n }\n }\n }\n }\n function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {\n let oldStartIdx = 0;\n let newStartIdx = 0;\n let oldEndIdx = oldCh.length - 1;\n let oldStartVnode = oldCh[0];\n let oldEndVnode = oldCh[oldEndIdx];\n let newEndIdx = newCh.length - 1;\n let newStartVnode = newCh[0];\n let newEndVnode = newCh[newEndIdx];\n let oldKeyToIdx;\n let idxInOld;\n let elmToMove;\n let before;\n while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n if (oldStartVnode == null) {\n oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left\n }\n else if (oldEndVnode == null) {\n oldEndVnode = oldCh[--oldEndIdx];\n }\n else if (newStartVnode == null) {\n newStartVnode = newCh[++newStartIdx];\n }\n else if (newEndVnode == null) {\n newEndVnode = newCh[--newEndIdx];\n }\n else if (sameVnode(oldStartVnode, newStartVnode)) {\n patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);\n oldStartVnode = oldCh[++oldStartIdx];\n newStartVnode = newCh[++newStartIdx];\n }\n else if (sameVnode(oldEndVnode, newEndVnode)) {\n patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);\n oldEndVnode = oldCh[--oldEndIdx];\n newEndVnode = newCh[--newEndIdx];\n }\n else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);\n api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));\n oldStartVnode = oldCh[++oldStartIdx];\n newEndVnode = newCh[--newEndIdx];\n }\n else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);\n api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);\n oldEndVnode = oldCh[--oldEndIdx];\n newStartVnode = newCh[++newStartIdx];\n }\n else {\n if (oldKeyToIdx === undefined) {\n oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);\n }\n idxInOld = oldKeyToIdx[newStartVnode.key];\n if (isUndef(idxInOld)) { // New element\n api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);\n }\n else {\n elmToMove = oldCh[idxInOld];\n if (elmToMove.sel !== newStartVnode.sel) {\n api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);\n }\n else {\n patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);\n oldCh[idxInOld] = undefined;\n api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);\n }\n }\n newStartVnode = newCh[++newStartIdx];\n }\n }\n if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {\n if (oldStartIdx > oldEndIdx) {\n before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;\n addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);\n }\n else {\n removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);\n }\n }\n }\n function patchVnode(oldVnode, vnode, insertedVnodeQueue) {\n var _a, _b, _c, _d, _e;\n const hook = (_a = vnode.data) === null || _a === void 0 ? void 0 : _a.hook;\n (_b = hook === null || hook === void 0 ? void 0 : hook.prepatch) === null || _b === void 0 ? void 0 : _b.call(hook, oldVnode, vnode);\n const elm = vnode.elm = oldVnode.elm;\n const oldCh = oldVnode.children;\n const ch = vnode.children;\n if (oldVnode === vnode)\n return;\n if (vnode.data !== undefined) {\n for (let i = 0; i < cbs.update.length; ++i)\n cbs.update[i](oldVnode, vnode);\n (_d = (_c = vnode.data.hook) === null || _c === void 0 ? void 0 : _c.update) === null || _d === void 0 ? void 0 : _d.call(_c, oldVnode, vnode);\n }\n if (isUndef(vnode.text)) {\n if (isDef(oldCh) && isDef(ch)) {\n if (oldCh !== ch)\n updateChildren(elm, oldCh, ch, insertedVnodeQueue);\n }\n else if (isDef(ch)) {\n if (isDef(oldVnode.text))\n api.setTextContent(elm, '');\n addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);\n }\n else if (isDef(oldCh)) {\n removeVnodes(elm, oldCh, 0, oldCh.length - 1);\n }\n else if (isDef(oldVnode.text)) {\n api.setTextContent(elm, '');\n }\n }\n else if (oldVnode.text !== vnode.text) {\n if (isDef(oldCh)) {\n removeVnodes(elm, oldCh, 0, oldCh.length - 1);\n }\n api.setTextContent(elm, vnode.text);\n }\n (_e = hook === null || hook === void 0 ? void 0 : hook.postpatch) === null || _e === void 0 ? void 0 : _e.call(hook, oldVnode, vnode);\n }\n return function patch(oldVnode, vnode) {\n let i, elm, parent;\n const insertedVnodeQueue = [];\n for (i = 0; i < cbs.pre.length; ++i)\n cbs.pre[i]();\n if (!isVnode(oldVnode)) {\n oldVnode = emptyNodeAt(oldVnode);\n }\n if (sameVnode(oldVnode, vnode)) {\n patchVnode(oldVnode, vnode, insertedVnodeQueue);\n }\n else {\n elm = oldVnode.elm;\n parent = api.parentNode(elm);\n createElm(vnode, insertedVnodeQueue);\n if (parent !== null) {\n api.insertBefore(parent, vnode.elm, api.nextSibling(elm));\n removeVnodes(parent, [oldVnode], 0, 0);\n }\n }\n for (i = 0; i < insertedVnodeQueue.length; ++i) {\n insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);\n }\n for (i = 0; i < cbs.post.length; ++i)\n cbs.post[i]();\n return vnode;\n };\n}\n//# sourceMappingURL=init.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9pbml0LmpzP2U5ZmYiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFtQztBQUNMO0FBQ2U7QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLGdEQUFLLE9BQU87QUFDOUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMEJBQTBCLGFBQWE7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnREFBZ0Qsc0RBQVU7QUFDMUQsZUFBZSxrQkFBa0I7QUFDakM7QUFDQSxtQkFBbUIsb0JBQW9CO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWUsZ0RBQUssNENBQTRDO0FBQ2hFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVCQUF1Qix1QkFBdUI7QUFDOUM7QUFDQSxnQkFBZ0IseUNBQVE7QUFDeEIsMkJBQTJCLHFCQUFxQjtBQUNoRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxQkFBcUIsNkNBQVk7QUFDakM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjLG9CQUFvQjtBQUNsQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMkJBQTJCLHdCQUF3QjtBQUNuRDtBQUNBO0FBQ0EsK0JBQStCLDJCQUEyQjtBQUMxRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWMsb0JBQW9CO0FBQ2xDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBbUMsdUJBQXVCO0FBQzFEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQjtBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscURBQXFEO0FBQ3JEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw2REFBNkQ7QUFDN0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZEQUE2RDtBQUM3RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdDQUF3QztBQUN4QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyQkFBMkIsdUJBQXVCO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQW1CLG9CQUFvQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFtQiwrQkFBK0I7QUFDbEQ7QUFDQTtBQUNBLG1CQUFtQixxQkFBcUI7QUFDeEM7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiIyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgdm5vZGUgfSBmcm9tIFwiLi92bm9kZS5qc1wiO1xuaW1wb3J0ICogYXMgaXMgZnJvbSBcIi4vaXMuanNcIjtcbmltcG9ydCB7IGh0bWxEb21BcGkgfSBmcm9tIFwiLi9odG1sZG9tYXBpLmpzXCI7XG5mdW5jdGlvbiBpc1VuZGVmKHMpIHtcbiAgICByZXR1cm4gcyA9PT0gdW5kZWZpbmVkO1xufVxuZnVuY3Rpb24gaXNEZWYocykge1xuICAgIHJldHVybiBzICE9PSB1bmRlZmluZWQ7XG59XG5jb25zdCBlbXB0eU5vZGUgPSB2bm9kZSgnJywge30sIFtdLCB1bmRlZmluZWQsIHVuZGVmaW5lZCk7XG5mdW5jdGlvbiBzYW1lVm5vZGUodm5vZGUxLCB2bm9kZTIpIHtcbiAgICByZXR1cm4gdm5vZGUxLmtleSA9PT0gdm5vZGUyLmtleSAmJiB2bm9kZTEuc2VsID09PSB2bm9kZTIuc2VsO1xufVxuZnVuY3Rpb24gaXNWbm9kZSh2bm9kZSkge1xuICAgIHJldHVybiB2bm9kZS5zZWwgIT09IHVuZGVmaW5lZDtcbn1cbmZ1bmN0aW9uIGNyZWF0ZUtleVRvT2xkSWR4KGNoaWxkcmVuLCBiZWdpbklkeCwgZW5kSWR4KSB7XG4gICAgdmFyIF9hO1xuICAgIGNvbnN0IG1hcCA9IHt9O1xuICAgIGZvciAobGV0IGkgPSBiZWdpbklkeDsgaSA8PSBlbmRJZHg7ICsraSkge1xuICAgICAgICBjb25zdCBrZXkgPSAoX2EgPSBjaGlsZHJlbltpXSkgPT09IG51bGwgfHwgX2EgPT09IHZvaWQgMCA/IHZvaWQgMCA6IF9hLmtleTtcbiAgICAgICAgaWYgKGtleSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBtYXBba2V5XSA9IGk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG1hcDtcbn1cbmNvbnN0IGhvb2tzID0gWydjcmVhdGUnLCAndXBkYXRlJywgJ3JlbW92ZScsICdkZXN0cm95JywgJ3ByZScsICdwb3N0J107XG5leHBvcnQgZnVuY3Rpb24gaW5pdChtb2R1bGVzLCBkb21BcGkpIHtcbiAgICBsZXQgaTtcbiAgICBsZXQgajtcbiAgICBjb25zdCBjYnMgPSB7XG4gICAgICAgIGNyZWF0ZTogW10sXG4gICAgICAgIHVwZGF0ZTogW10sXG4gICAgICAgIHJlbW92ZTogW10sXG4gICAgICAgIGRlc3Ryb3k6IFtdLFxuICAgICAgICBwcmU6IFtdLFxuICAgICAgICBwb3N0OiBbXVxuICAgIH07XG4gICAgY29uc3QgYXBpID0gZG9tQXBpICE9PSB1bmRlZmluZWQgPyBkb21BcGkgOiBodG1sRG9tQXBpO1xuICAgIGZvciAoaSA9IDA7IGkgPCBob29rcy5sZW5ndGg7ICsraSkge1xuICAgICAgICBjYnNbaG9va3NbaV1dID0gW107XG4gICAgICAgIGZvciAoaiA9IDA7IGogPCBtb2R1bGVzLmxlbmd0aDsgKytqKSB7XG4gICAgICAgICAgICBjb25zdCBob29rID0gbW9kdWxlc1tqXVtob29rc1tpXV07XG4gICAgICAgICAgICBpZiAoaG9vayAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgY2JzW2hvb2tzW2ldXS5wdXNoKGhvb2spO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGZ1bmN0aW9uIGVtcHR5Tm9kZUF0KGVsbSkge1xuICAgICAgICBjb25zdCBpZCA9IGVsbS5pZCA/ICcjJyArIGVsbS5pZCA6ICcnO1xuICAgICAgICBjb25zdCBjID0gZWxtLmNsYXNzTmFtZSA/ICcuJyArIGVsbS5jbGFzc05hbWUuc3BsaXQoJyAnKS5qb2luKCcuJykgOiAnJztcbiAgICAgICAgcmV0dXJuIHZub2RlKGFwaS50YWdOYW1lKGVsbSkudG9Mb3dlckNhc2UoKSArIGlkICsgYywge30sIFtdLCB1bmRlZmluZWQsIGVsbSk7XG4gICAgfVxuICAgIGZ1bmN0aW9uIGNyZWF0ZVJtQ2IoY2hpbGRFbG0sIGxpc3RlbmVycykge1xuICAgICAgICByZXR1cm4gZnVuY3Rpb24gcm1DYigpIHtcbiAgICAgICAgICAgIGlmICgtLWxpc3RlbmVycyA9PT0gMCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHBhcmVudCA9IGFwaS5wYXJlbnROb2RlKGNoaWxkRWxtKTtcbiAgICAgICAgICAgICAgICBhcGkucmVtb3ZlQ2hpbGQocGFyZW50LCBjaGlsZEVsbSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgfVxuICAgIGZ1bmN0aW9uIGNyZWF0ZUVsbSh2bm9kZSwgaW5zZXJ0ZWRWbm9kZVF1ZXVlKSB7XG4gICAgICAgIHZhciBfYSwgX2I7XG4gICAgICAgIGxldCBpO1xuICAgICAgICBsZXQgZGF0YSA9IHZub2RlLmRhdGE7XG4gICAgICAgIGlmIChkYXRhICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGNvbnN0IGluaXQgPSAoX2EgPSBkYXRhLmhvb2spID09PSBudWxsIHx8IF9hID09PSB2b2lkIDAgPyB2b2lkIDAgOiBfYS5pbml0O1xuICAgICAgICAgICAgaWYgKGlzRGVmKGluaXQpKSB7XG4gICAgICAgICAgICAgICAgaW5pdCh2bm9kZSk7XG4gICAgICAgICAgICAgICAgZGF0YSA9IHZub2RlLmRhdGE7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgY2hpbGRyZW4gPSB2bm9kZS5jaGlsZHJlbjtcbiAgICAgICAgY29uc3Qgc2VsID0gdm5vZGUuc2VsO1xuICAgICAgICBpZiAoc2VsID09PSAnIScpIHtcbiAgICAgICAgICAgIGlmIChpc1VuZGVmKHZub2RlLnRleHQpKSB7XG4gICAgICAgICAgICAgICAgdm5vZGUudGV4dCA9ICcnO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdm5vZGUuZWxtID0gYXBpLmNyZWF0ZUNvbW1lbnQodm5vZGUudGV4dCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoc2VsICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIC8vIFBhcnNlIHNlbGVjdG9yXG4gICAgICAgICAgICBjb25zdCBoYXNoSWR4ID0gc2VsLmluZGV4T2YoJyMnKTtcbiAgICAgICAgICAgIGNvbnN0IGRvdElkeCA9IHNlbC5pbmRleE9mKCcuJywgaGFzaElkeCk7XG4gICAgICAgICAgICBjb25zdCBoYXNoID0gaGFzaElkeCA+IDAgPyBoYXNoSWR4IDogc2VsLmxlbmd0aDtcbiAgICAgICAgICAgIGNvbnN0IGRvdCA9IGRvdElkeCA+IDAgPyBkb3RJZHggOiBzZWwubGVuZ3RoO1xuICAgICAgICAgICAgY29uc3QgdGFnID0gaGFzaElkeCAhPT0gLTEgfHwgZG90SWR4ICE9PSAtMSA/IHNlbC5zbGljZSgwLCBNYXRoLm1pbihoYXNoLCBkb3QpKSA6IHNlbDtcbiAgICAgICAgICAgIGNvbnN0IGVsbSA9IHZub2RlLmVsbSA9IGlzRGVmKGRhdGEpICYmIGlzRGVmKGkgPSBkYXRhLm5zKVxuICAgICAgICAgICAgICAgID8gYXBpLmNyZWF0ZUVsZW1lbnROUyhpLCB0YWcpXG4gICAgICAgICAgICAgICAgOiBhcGkuY3JlYXRlRWxlbWVudCh0YWcpO1xuICAgICAgICAgICAgaWYgKGhhc2ggPCBkb3QpXG4gICAgICAgICAgICAgICAgZWxtLnNldEF0dHJpYnV0ZSgnaWQnLCBzZWwuc2xpY2UoaGFzaCArIDEsIGRvdCkpO1xuICAgICAgICAgICAgaWYgKGRvdElkeCA+IDApXG4gICAgICAgICAgICAgICAgZWxtLnNldEF0dHJpYnV0ZSgnY2xhc3MnLCBzZWwuc2xpY2UoZG90ICsgMSkucmVwbGFjZSgvXFwuL2csICcgJykpO1xuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGNicy5jcmVhdGUubGVuZ3RoOyArK2kpXG4gICAgICAgICAgICAgICAgY2JzLmNyZWF0ZVtpXShlbXB0eU5vZGUsIHZub2RlKTtcbiAgICAgICAgICAgIGlmIChpcy5hcnJheShjaGlsZHJlbikpIHtcbiAgICAgICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgY2hpbGRyZW4ubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgY2ggPSBjaGlsZHJlbltpXTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGNoICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGFwaS5hcHBlbmRDaGlsZChlbG0sIGNyZWF0ZUVsbShjaCwgaW5zZXJ0ZWRWbm9kZVF1ZXVlKSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChpcy5wcmltaXRpdmUodm5vZGUudGV4dCkpIHtcbiAgICAgICAgICAgICAgICBhcGkuYXBwZW5kQ2hpbGQoZWxtLCBhcGkuY3JlYXRlVGV4dE5vZGUodm5vZGUudGV4dCkpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgaG9vayA9IHZub2RlLmRhdGEuaG9vaztcbiAgICAgICAgICAgIGlmIChpc0RlZihob29rKSkge1xuICAgICAgICAgICAgICAgIChfYiA9IGhvb2suY3JlYXRlKSA9PT0gbnVsbCB8fCBfYiA9PT0gdm9pZCAwID8gdm9pZCAwIDogX2IuY2FsbChob29rLCBlbXB0eU5vZGUsIHZub2RlKTtcbiAgICAgICAgICAgICAgICBpZiAoaG9vay5pbnNlcnQpIHtcbiAgICAgICAgICAgICAgICAgICAgaW5zZXJ0ZWRWbm9kZVF1ZXVlLnB1c2godm5vZGUpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZub2RlLmVsbSA9IGFwaS5jcmVhdGVUZXh0Tm9kZSh2bm9kZS50ZXh0KTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdm5vZGUuZWxtO1xuICAgIH1cbiAgICBmdW5jdGlvbiBhZGRWbm9kZXMocGFyZW50RWxtLCBiZWZvcmUsIHZub2Rlcywgc3RhcnRJZHgsIGVuZElkeCwgaW5zZXJ0ZWRWbm9kZVF1ZXVlKSB7XG4gICAgICAgIGZvciAoOyBzdGFydElkeCA8PSBlbmRJZHg7ICsrc3RhcnRJZHgpIHtcbiAgICAgICAgICAgIGNvbnN0IGNoID0gdm5vZGVzW3N0YXJ0SWR4XTtcbiAgICAgICAgICAgIGlmIChjaCAhPSBudWxsKSB7XG4gICAgICAgICAgICAgICAgYXBpLmluc2VydEJlZm9yZShwYXJlbnRFbG0sIGNyZWF0ZUVsbShjaCwgaW5zZXJ0ZWRWbm9kZVF1ZXVlKSwgYmVmb3JlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICBmdW5jdGlvbiBpbnZva2VEZXN0cm95SG9vayh2bm9kZSkge1xuICAgICAgICB2YXIgX2EsIF9iO1xuICAgICAgICBjb25zdCBkYXRhID0gdm5vZGUuZGF0YTtcbiAgICAgICAgaWYgKGRhdGEgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgKF9iID0gKF9hID0gZGF0YSA9PT0gbnVsbCB8fCBkYXRhID09PSB2b2lkIDAgPyB2b2lkIDAgOiBkYXRhLmhvb2spID09PSBudWxsIHx8IF9hID09PSB2b2lkIDAgPyB2b2lkIDAgOiBfYS5kZXN0cm95KSA9PT0gbnVsbCB8fCBfYiA9PT0gdm9pZCAwID8gdm9pZCAwIDogX2IuY2FsbChfYSwgdm5vZGUpO1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBjYnMuZGVzdHJveS5sZW5ndGg7ICsraSlcbiAgICAgICAgICAgICAgICBjYnMuZGVzdHJveVtpXSh2bm9kZSk7XG4gICAgICAgICAgICBpZiAodm5vZGUuY2hpbGRyZW4gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgdm5vZGUuY2hpbGRyZW4ubGVuZ3RoOyArK2opIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgY2hpbGQgPSB2bm9kZS5jaGlsZHJlbltqXTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGNoaWxkICE9IG51bGwgJiYgdHlwZW9mIGNoaWxkICE9PSAnc3RyaW5nJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgaW52b2tlRGVzdHJveUhvb2soY2hpbGQpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGZ1bmN0aW9uIHJlbW92ZVZub2RlcyhwYXJlbnRFbG0sIHZub2Rlcywgc3RhcnRJZHgsIGVuZElkeCkge1xuICAgICAgICB2YXIgX2EsIF9iO1xuICAgICAgICBmb3IgKDsgc3RhcnRJZHggPD0gZW5kSWR4OyArK3N0YXJ0SWR4KSB7XG4gICAgICAgICAgICBsZXQgbGlzdGVuZXJzO1xuICAgICAgICAgICAgbGV0IHJtO1xuICAgICAgICAgICAgY29uc3QgY2ggPSB2bm9kZXNbc3RhcnRJZHhdO1xuICAgICAgICAgICAgaWYgKGNoICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgICBpZiAoaXNEZWYoY2guc2VsKSkge1xuICAgICAgICAgICAgICAgICAgICBpbnZva2VEZXN0cm95SG9vayhjaCk7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycyA9IGNicy5yZW1vdmUubGVuZ3RoICsgMTtcbiAgICAgICAgICAgICAgICAgICAgcm0gPSBjcmVhdGVSbUNiKGNoLmVsbSwgbGlzdGVuZXJzKTtcbiAgICAgICAgICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBjYnMucmVtb3ZlLmxlbmd0aDsgKytpKVxuICAgICAgICAgICAgICAgICAgICAgICAgY2JzLnJlbW92ZVtpXShjaCwgcm0pO1xuICAgICAgICAgICAgICAgICAgICBjb25zdCByZW1vdmVIb29rID0gKF9iID0gKF9hID0gY2ggPT09IG51bGwgfHwgY2ggPT09IHZvaWQgMCA/IHZvaWQgMCA6IGNoLmRhdGEpID09PSBudWxsIHx8IF9hID09PSB2b2lkIDAgPyB2b2lkIDAgOiBfYS5ob29rKSA9PT0gbnVsbCB8fCBfYiA9PT0gdm9pZCAwID8gdm9pZCAwIDogX2IucmVtb3ZlO1xuICAgICAgICAgICAgICAgICAgICBpZiAoaXNEZWYocmVtb3ZlSG9vaykpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZUhvb2soY2gsIHJtKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJtKCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSB7IC8vIFRleHQgbm9kZVxuICAgICAgICAgICAgICAgICAgICBhcGkucmVtb3ZlQ2hpbGQocGFyZW50RWxtLCBjaC5lbG0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICBmdW5jdGlvbiB1cGRhdGVDaGlsZHJlbihwYXJlbnRFbG0sIG9sZENoLCBuZXdDaCwgaW5zZXJ0ZWRWbm9kZVF1ZXVlKSB7XG4gICAgICAgIGxldCBvbGRTdGFydElkeCA9IDA7XG4gICAgICAgIGxldCBuZXdTdGFydElkeCA9IDA7XG4gICAgICAgIGxldCBvbGRFbmRJZHggPSBvbGRDaC5sZW5ndGggLSAxO1xuICAgICAgICBsZXQgb2xkU3RhcnRWbm9kZSA9IG9sZENoWzBdO1xuICAgICAgICBsZXQgb2xkRW5kVm5vZGUgPSBvbGRDaFtvbGRFbmRJZHhdO1xuICAgICAgICBsZXQgbmV3RW5kSWR4ID0gbmV3Q2gubGVuZ3RoIC0gMTtcbiAgICAgICAgbGV0IG5ld1N0YXJ0Vm5vZGUgPSBuZXdDaFswXTtcbiAgICAgICAgbGV0IG5ld0VuZFZub2RlID0gbmV3Q2hbbmV3RW5kSWR4XTtcbiAgICAgICAgbGV0IG9sZEtleVRvSWR4O1xuICAgICAgICBsZXQgaWR4SW5PbGQ7XG4gICAgICAgIGxldCBlbG1Ub01vdmU7XG4gICAgICAgIGxldCBiZWZvcmU7XG4gICAgICAgIHdoaWxlIChvbGRTdGFydElkeCA8PSBvbGRFbmRJZHggJiYgbmV3U3RhcnRJZHggPD0gbmV3RW5kSWR4KSB7XG4gICAgICAgICAgICBpZiAob2xkU3RhcnRWbm9kZSA9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgb2xkU3RhcnRWbm9kZSA9IG9sZENoWysrb2xkU3RhcnRJZHhdOyAvLyBWbm9kZSBtaWdodCBoYXZlIGJlZW4gbW92ZWQgbGVmdFxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAob2xkRW5kVm5vZGUgPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIG9sZEVuZFZub2RlID0gb2xkQ2hbLS1vbGRFbmRJZHhdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAobmV3U3RhcnRWbm9kZSA9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgbmV3U3RhcnRWbm9kZSA9IG5ld0NoWysrbmV3U3RhcnRJZHhdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAobmV3RW5kVm5vZGUgPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIG5ld0VuZFZub2RlID0gbmV3Q2hbLS1uZXdFbmRJZHhdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoc2FtZVZub2RlKG9sZFN0YXJ0Vm5vZGUsIG5ld1N0YXJ0Vm5vZGUpKSB7XG4gICAgICAgICAgICAgICAgcGF0Y2hWbm9kZShvbGRTdGFydFZub2RlLCBuZXdTdGFydFZub2RlLCBpbnNlcnRlZFZub2RlUXVldWUpO1xuICAgICAgICAgICAgICAgIG9sZFN0YXJ0Vm5vZGUgPSBvbGRDaFsrK29sZFN0YXJ0SWR4XTtcbiAgICAgICAgICAgICAgICBuZXdTdGFydFZub2RlID0gbmV3Q2hbKytuZXdTdGFydElkeF07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChzYW1lVm5vZGUob2xkRW5kVm5vZGUsIG5ld0VuZFZub2RlKSkge1xuICAgICAgICAgICAgICAgIHBhdGNoVm5vZGUob2xkRW5kVm5vZGUsIG5ld0VuZFZub2RlLCBpbnNlcnRlZFZub2RlUXVldWUpO1xuICAgICAgICAgICAgICAgIG9sZEVuZFZub2RlID0gb2xkQ2hbLS1vbGRFbmRJZHhdO1xuICAgICAgICAgICAgICAgIG5ld0VuZFZub2RlID0gbmV3Q2hbLS1uZXdFbmRJZHhdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoc2FtZVZub2RlKG9sZFN0YXJ0Vm5vZGUsIG5ld0VuZFZub2RlKSkgeyAvLyBWbm9kZSBtb3ZlZCByaWdodFxuICAgICAgICAgICAgICAgIHBhdGNoVm5vZGUob2xkU3RhcnRWbm9kZSwgbmV3RW5kVm5vZGUsIGluc2VydGVkVm5vZGVRdWV1ZSk7XG4gICAgICAgICAgICAgICAgYXBpLmluc2VydEJlZm9yZShwYXJlbnRFbG0sIG9sZFN0YXJ0Vm5vZGUuZWxtLCBhcGkubmV4dFNpYmxpbmcob2xkRW5kVm5vZGUuZWxtKSk7XG4gICAgICAgICAgICAgICAgb2xkU3RhcnRWbm9kZSA9IG9sZENoWysrb2xkU3RhcnRJZHhdO1xuICAgICAgICAgICAgICAgIG5ld0VuZFZub2RlID0gbmV3Q2hbLS1uZXdFbmRJZHhdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoc2FtZVZub2RlKG9sZEVuZFZub2RlLCBuZXdTdGFydFZub2RlKSkgeyAvLyBWbm9kZSBtb3ZlZCBsZWZ0XG4gICAgICAgICAgICAgICAgcGF0Y2hWbm9kZShvbGRFbmRWbm9kZSwgbmV3U3RhcnRWbm9kZSwgaW5zZXJ0ZWRWbm9kZVF1ZXVlKTtcbiAgICAgICAgICAgICAgICBhcGkuaW5zZXJ0QmVmb3JlKHBhcmVudEVsbSwgb2xkRW5kVm5vZGUuZWxtLCBvbGRTdGFydFZub2RlLmVsbSk7XG4gICAgICAgICAgICAgICAgb2xkRW5kVm5vZGUgPSBvbGRDaFstLW9sZEVuZElkeF07XG4gICAgICAgICAgICAgICAgbmV3U3RhcnRWbm9kZSA9IG5ld0NoWysrbmV3U3RhcnRJZHhdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgaWYgKG9sZEtleVRvSWR4ID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICAgICAgb2xkS2V5VG9JZHggPSBjcmVhdGVLZXlUb09sZElkeChvbGRDaCwgb2xkU3RhcnRJZHgsIG9sZEVuZElkeCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlkeEluT2xkID0gb2xkS2V5VG9JZHhbbmV3U3RhcnRWbm9kZS5rZXldO1xuICAgICAgICAgICAgICAgIGlmIChpc1VuZGVmKGlkeEluT2xkKSkgeyAvLyBOZXcgZWxlbWVudFxuICAgICAgICAgICAgICAgICAgICBhcGkuaW5zZXJ0QmVmb3JlKHBhcmVudEVsbSwgY3JlYXRlRWxtKG5ld1N0YXJ0Vm5vZGUsIGluc2VydGVkVm5vZGVRdWV1ZSksIG9sZFN0YXJ0Vm5vZGUuZWxtKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGVsbVRvTW92ZSA9IG9sZENoW2lkeEluT2xkXTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGVsbVRvTW92ZS5zZWwgIT09IG5ld1N0YXJ0Vm5vZGUuc2VsKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBhcGkuaW5zZXJ0QmVmb3JlKHBhcmVudEVsbSwgY3JlYXRlRWxtKG5ld1N0YXJ0Vm5vZGUsIGluc2VydGVkVm5vZGVRdWV1ZSksIG9sZFN0YXJ0Vm5vZGUuZWxtKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHBhdGNoVm5vZGUoZWxtVG9Nb3ZlLCBuZXdTdGFydFZub2RlLCBpbnNlcnRlZFZub2RlUXVldWUpO1xuICAgICAgICAgICAgICAgICAgICAgICAgb2xkQ2hbaWR4SW5PbGRdID0gdW5kZWZpbmVkO1xuICAgICAgICAgICAgICAgICAgICAgICAgYXBpLmluc2VydEJlZm9yZShwYXJlbnRFbG0sIGVsbVRvTW92ZS5lbG0sIG9sZFN0YXJ0Vm5vZGUuZWxtKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBuZXdTdGFydFZub2RlID0gbmV3Q2hbKytuZXdTdGFydElkeF07XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgaWYgKG9sZFN0YXJ0SWR4IDw9IG9sZEVuZElkeCB8fCBuZXdTdGFydElkeCA8PSBuZXdFbmRJZHgpIHtcbiAgICAgICAgICAgIGlmIChvbGRTdGFydElkeCA+IG9sZEVuZElkeCkge1xuICAgICAgICAgICAgICAgIGJlZm9yZSA9IG5ld0NoW25ld0VuZElkeCArIDFdID09IG51bGwgPyBudWxsIDogbmV3Q2hbbmV3RW5kSWR4ICsgMV0uZWxtO1xuICAgICAgICAgICAgICAgIGFkZFZub2RlcyhwYXJlbnRFbG0sIGJlZm9yZSwgbmV3Q2gsIG5ld1N0YXJ0SWR4LCBuZXdFbmRJZHgsIGluc2VydGVkVm5vZGVRdWV1ZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICByZW1vdmVWbm9kZXMocGFyZW50RWxtLCBvbGRDaCwgb2xkU3RhcnRJZHgsIG9sZEVuZElkeCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgZnVuY3Rpb24gcGF0Y2hWbm9kZShvbGRWbm9kZSwgdm5vZGUsIGluc2VydGVkVm5vZGVRdWV1ZSkge1xuICAgICAgICB2YXIgX2EsIF9iLCBfYywgX2QsIF9lO1xuICAgICAgICBjb25zdCBob29rID0gKF9hID0gdm5vZGUuZGF0YSkgPT09IG51bGwgfHwgX2EgPT09IHZvaWQgMCA/IHZvaWQgMCA6IF9hLmhvb2s7XG4gICAgICAgIChfYiA9IGhvb2sgPT09IG51bGwgfHwgaG9vayA9PT0gdm9pZCAwID8gdm9pZCAwIDogaG9vay5wcmVwYXRjaCkgPT09IG51bGwgfHwgX2IgPT09IHZvaWQgMCA/IHZvaWQgMCA6IF9iLmNhbGwoaG9vaywgb2xkVm5vZGUsIHZub2RlKTtcbiAgICAgICAgY29uc3QgZWxtID0gdm5vZGUuZWxtID0gb2xkVm5vZGUuZWxtO1xuICAgICAgICBjb25zdCBvbGRDaCA9IG9sZFZub2RlLmNoaWxkcmVuO1xuICAgICAgICBjb25zdCBjaCA9IHZub2RlLmNoaWxkcmVuO1xuICAgICAgICBpZiAob2xkVm5vZGUgPT09IHZub2RlKVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBpZiAodm5vZGUuZGF0YSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGNicy51cGRhdGUubGVuZ3RoOyArK2kpXG4gICAgICAgICAgICAgICAgY2JzLnVwZGF0ZVtpXShvbGRWbm9kZSwgdm5vZGUpO1xuICAgICAgICAgICAgKF9kID0gKF9jID0gdm5vZGUuZGF0YS5ob29rKSA9PT0gbnVsbCB8fCBfYyA9PT0gdm9pZCAwID8gdm9pZCAwIDogX2MudXBkYXRlKSA9PT0gbnVsbCB8fCBfZCA9PT0gdm9pZCAwID8gdm9pZCAwIDogX2QuY2FsbChfYywgb2xkVm5vZGUsIHZub2RlKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoaXNVbmRlZih2bm9kZS50ZXh0KSkge1xuICAgICAgICAgICAgaWYgKGlzRGVmKG9sZENoKSAmJiBpc0RlZihjaCkpIHtcbiAgICAgICAgICAgICAgICBpZiAob2xkQ2ggIT09IGNoKVxuICAgICAgICAgICAgICAgICAgICB1cGRhdGVDaGlsZHJlbihlbG0sIG9sZENoLCBjaCwgaW5zZXJ0ZWRWbm9kZVF1ZXVlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGlzRGVmKGNoKSkge1xuICAgICAgICAgICAgICAgIGlmIChpc0RlZihvbGRWbm9kZS50ZXh0KSlcbiAgICAgICAgICAgICAgICAgICAgYXBpLnNldFRleHRDb250ZW50KGVsbSwgJycpO1xuICAgICAgICAgICAgICAgIGFkZFZub2RlcyhlbG0sIG51bGwsIGNoLCAwLCBjaC5sZW5ndGggLSAxLCBpbnNlcnRlZFZub2RlUXVldWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoaXNEZWYob2xkQ2gpKSB7XG4gICAgICAgICAgICAgICAgcmVtb3ZlVm5vZGVzKGVsbSwgb2xkQ2gsIDAsIG9sZENoLmxlbmd0aCAtIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoaXNEZWYob2xkVm5vZGUudGV4dCkpIHtcbiAgICAgICAgICAgICAgICBhcGkuc2V0VGV4dENvbnRlbnQoZWxtLCAnJyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAob2xkVm5vZGUudGV4dCAhPT0gdm5vZGUudGV4dCkge1xuICAgICAgICAgICAgaWYgKGlzRGVmKG9sZENoKSkge1xuICAgICAgICAgICAgICAgIHJlbW92ZVZub2RlcyhlbG0sIG9sZENoLCAwLCBvbGRDaC5sZW5ndGggLSAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGFwaS5zZXRUZXh0Q29udGVudChlbG0sIHZub2RlLnRleHQpO1xuICAgICAgICB9XG4gICAgICAgIChfZSA9IGhvb2sgPT09IG51bGwgfHwgaG9vayA9PT0gdm9pZCAwID8gdm9pZCAwIDogaG9vay5wb3N0cGF0Y2gpID09PSBudWxsIHx8IF9lID09PSB2b2lkIDAgPyB2b2lkIDAgOiBfZS5jYWxsKGhvb2ssIG9sZFZub2RlLCB2bm9kZSk7XG4gICAgfVxuICAgIHJldHVybiBmdW5jdGlvbiBwYXRjaChvbGRWbm9kZSwgdm5vZGUpIHtcbiAgICAgICAgbGV0IGksIGVsbSwgcGFyZW50O1xuICAgICAgICBjb25zdCBpbnNlcnRlZFZub2RlUXVldWUgPSBbXTtcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGNicy5wcmUubGVuZ3RoOyArK2kpXG4gICAgICAgICAgICBjYnMucHJlW2ldKCk7XG4gICAgICAgIGlmICghaXNWbm9kZShvbGRWbm9kZSkpIHtcbiAgICAgICAgICAgIG9sZFZub2RlID0gZW1wdHlOb2RlQXQob2xkVm5vZGUpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChzYW1lVm5vZGUob2xkVm5vZGUsIHZub2RlKSkge1xuICAgICAgICAgICAgcGF0Y2hWbm9kZShvbGRWbm9kZSwgdm5vZGUsIGluc2VydGVkVm5vZGVRdWV1ZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBlbG0gPSBvbGRWbm9kZS5lbG07XG4gICAgICAgICAgICBwYXJlbnQgPSBhcGkucGFyZW50Tm9kZShlbG0pO1xuICAgICAgICAgICAgY3JlYXRlRWxtKHZub2RlLCBpbnNlcnRlZFZub2RlUXVldWUpO1xuICAgICAgICAgICAgaWYgKHBhcmVudCAhPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIGFwaS5pbnNlcnRCZWZvcmUocGFyZW50LCB2bm9kZS5lbG0sIGFwaS5uZXh0U2libGluZyhlbG0pKTtcbiAgICAgICAgICAgICAgICByZW1vdmVWbm9kZXMocGFyZW50LCBbb2xkVm5vZGVdLCAwLCAwKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgaW5zZXJ0ZWRWbm9kZVF1ZXVlLmxlbmd0aDsgKytpKSB7XG4gICAgICAgICAgICBpbnNlcnRlZFZub2RlUXVldWVbaV0uZGF0YS5ob29rLmluc2VydChpbnNlcnRlZFZub2RlUXVldWVbaV0pO1xuICAgICAgICB9XG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBjYnMucG9zdC5sZW5ndGg7ICsraSlcbiAgICAgICAgICAgIGNicy5wb3N0W2ldKCk7XG4gICAgICAgIHJldHVybiB2bm9kZTtcbiAgICB9O1xufVxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9aW5pdC5qcy5tYXAiXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///2\n"); + +/***/ }), +/* 3 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"vnode\": () => (/* binding */ vnode)\n/* harmony export */ });\nfunction vnode(sel, data, children, text, elm) {\n const key = data === undefined ? undefined : data.key;\n return { sel, data, children, text, elm, key };\n}\n//# sourceMappingURL=vnode.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS92bm9kZS5qcz9iYTg4Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFBTztBQUNQO0FBQ0EsWUFBWTtBQUNaO0FBQ0EiLCJmaWxlIjoiMy5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBmdW5jdGlvbiB2bm9kZShzZWwsIGRhdGEsIGNoaWxkcmVuLCB0ZXh0LCBlbG0pIHtcbiAgICBjb25zdCBrZXkgPSBkYXRhID09PSB1bmRlZmluZWQgPyB1bmRlZmluZWQgOiBkYXRhLmtleTtcbiAgICByZXR1cm4geyBzZWwsIGRhdGEsIGNoaWxkcmVuLCB0ZXh0LCBlbG0sIGtleSB9O1xufVxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9dm5vZGUuanMubWFwIl0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///3\n"); + +/***/ }), +/* 4 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"array\": () => (/* binding */ array),\n/* harmony export */ \"primitive\": () => (/* binding */ primitive)\n/* harmony export */ });\nconst array = Array.isArray;\nfunction primitive(s) {\n return typeof s === 'string' || typeof s === 'number';\n}\n//# sourceMappingURL=is.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9pcy5qcz9lNTQ1Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBQU87QUFDQTtBQUNQO0FBQ0E7QUFDQSIsImZpbGUiOiI0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGNvbnN0IGFycmF5ID0gQXJyYXkuaXNBcnJheTtcbmV4cG9ydCBmdW5jdGlvbiBwcmltaXRpdmUocykge1xuICAgIHJldHVybiB0eXBlb2YgcyA9PT0gJ3N0cmluZycgfHwgdHlwZW9mIHMgPT09ICdudW1iZXInO1xufVxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9aXMuanMubWFwIl0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///4\n"); + +/***/ }), +/* 5 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"htmlDomApi\": () => (/* binding */ htmlDomApi)\n/* harmony export */ });\nfunction createElement(tagName) {\n return document.createElement(tagName);\n}\nfunction createElementNS(namespaceURI, qualifiedName) {\n return document.createElementNS(namespaceURI, qualifiedName);\n}\nfunction createTextNode(text) {\n return document.createTextNode(text);\n}\nfunction createComment(text) {\n return document.createComment(text);\n}\nfunction insertBefore(parentNode, newNode, referenceNode) {\n parentNode.insertBefore(newNode, referenceNode);\n}\nfunction removeChild(node, child) {\n node.removeChild(child);\n}\nfunction appendChild(node, child) {\n node.appendChild(child);\n}\nfunction parentNode(node) {\n return node.parentNode;\n}\nfunction nextSibling(node) {\n return node.nextSibling;\n}\nfunction tagName(elm) {\n return elm.tagName;\n}\nfunction setTextContent(node, text) {\n node.textContent = text;\n}\nfunction getTextContent(node) {\n return node.textContent;\n}\nfunction isElement(node) {\n return node.nodeType === 1;\n}\nfunction isText(node) {\n return node.nodeType === 3;\n}\nfunction isComment(node) {\n return node.nodeType === 8;\n}\nconst htmlDomApi = {\n createElement,\n createElementNS,\n createTextNode,\n createComment,\n insertBefore,\n removeChild,\n appendChild,\n parentNode,\n nextSibling,\n tagName,\n setTextContent,\n getTextContent,\n isElement,\n isText,\n isComment,\n};\n//# sourceMappingURL=htmldomapi.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9odG1sZG9tYXBpLmpzPzQ3NDEiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiI1LmpzIiwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gY3JlYXRlRWxlbWVudCh0YWdOYW1lKSB7XG4gICAgcmV0dXJuIGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQodGFnTmFtZSk7XG59XG5mdW5jdGlvbiBjcmVhdGVFbGVtZW50TlMobmFtZXNwYWNlVVJJLCBxdWFsaWZpZWROYW1lKSB7XG4gICAgcmV0dXJuIGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyhuYW1lc3BhY2VVUkksIHF1YWxpZmllZE5hbWUpO1xufVxuZnVuY3Rpb24gY3JlYXRlVGV4dE5vZGUodGV4dCkge1xuICAgIHJldHVybiBkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZSh0ZXh0KTtcbn1cbmZ1bmN0aW9uIGNyZWF0ZUNvbW1lbnQodGV4dCkge1xuICAgIHJldHVybiBkb2N1bWVudC5jcmVhdGVDb21tZW50KHRleHQpO1xufVxuZnVuY3Rpb24gaW5zZXJ0QmVmb3JlKHBhcmVudE5vZGUsIG5ld05vZGUsIHJlZmVyZW5jZU5vZGUpIHtcbiAgICBwYXJlbnROb2RlLmluc2VydEJlZm9yZShuZXdOb2RlLCByZWZlcmVuY2VOb2RlKTtcbn1cbmZ1bmN0aW9uIHJlbW92ZUNoaWxkKG5vZGUsIGNoaWxkKSB7XG4gICAgbm9kZS5yZW1vdmVDaGlsZChjaGlsZCk7XG59XG5mdW5jdGlvbiBhcHBlbmRDaGlsZChub2RlLCBjaGlsZCkge1xuICAgIG5vZGUuYXBwZW5kQ2hpbGQoY2hpbGQpO1xufVxuZnVuY3Rpb24gcGFyZW50Tm9kZShub2RlKSB7XG4gICAgcmV0dXJuIG5vZGUucGFyZW50Tm9kZTtcbn1cbmZ1bmN0aW9uIG5leHRTaWJsaW5nKG5vZGUpIHtcbiAgICByZXR1cm4gbm9kZS5uZXh0U2libGluZztcbn1cbmZ1bmN0aW9uIHRhZ05hbWUoZWxtKSB7XG4gICAgcmV0dXJuIGVsbS50YWdOYW1lO1xufVxuZnVuY3Rpb24gc2V0VGV4dENvbnRlbnQobm9kZSwgdGV4dCkge1xuICAgIG5vZGUudGV4dENvbnRlbnQgPSB0ZXh0O1xufVxuZnVuY3Rpb24gZ2V0VGV4dENvbnRlbnQobm9kZSkge1xuICAgIHJldHVybiBub2RlLnRleHRDb250ZW50O1xufVxuZnVuY3Rpb24gaXNFbGVtZW50KG5vZGUpIHtcbiAgICByZXR1cm4gbm9kZS5ub2RlVHlwZSA9PT0gMTtcbn1cbmZ1bmN0aW9uIGlzVGV4dChub2RlKSB7XG4gICAgcmV0dXJuIG5vZGUubm9kZVR5cGUgPT09IDM7XG59XG5mdW5jdGlvbiBpc0NvbW1lbnQobm9kZSkge1xuICAgIHJldHVybiBub2RlLm5vZGVUeXBlID09PSA4O1xufVxuZXhwb3J0IGNvbnN0IGh0bWxEb21BcGkgPSB7XG4gICAgY3JlYXRlRWxlbWVudCxcbiAgICBjcmVhdGVFbGVtZW50TlMsXG4gICAgY3JlYXRlVGV4dE5vZGUsXG4gICAgY3JlYXRlQ29tbWVudCxcbiAgICBpbnNlcnRCZWZvcmUsXG4gICAgcmVtb3ZlQ2hpbGQsXG4gICAgYXBwZW5kQ2hpbGQsXG4gICAgcGFyZW50Tm9kZSxcbiAgICBuZXh0U2libGluZyxcbiAgICB0YWdOYW1lLFxuICAgIHNldFRleHRDb250ZW50LFxuICAgIGdldFRleHRDb250ZW50LFxuICAgIGlzRWxlbWVudCxcbiAgICBpc1RleHQsXG4gICAgaXNDb21tZW50LFxufTtcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWh0bWxkb21hcGkuanMubWFwIl0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///5\n"); + +/***/ }), +/* 6 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"h\": () => (/* binding */ h)\n/* harmony export */ });\n/* harmony import */ var _vnode_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);\n/* harmony import */ var _is_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);\n\n\nfunction addNS(data, children, sel) {\n data.ns = 'http://www.w3.org/2000/svg';\n if (sel !== 'foreignObject' && children !== undefined) {\n for (let i = 0; i < children.length; ++i) {\n const childData = children[i].data;\n if (childData !== undefined) {\n addNS(childData, children[i].children, children[i].sel);\n }\n }\n }\n}\nfunction h(sel, b, c) {\n var data = {};\n var children;\n var text;\n var i;\n if (c !== undefined) {\n if (b !== null) {\n data = b;\n }\n if (_is_js__WEBPACK_IMPORTED_MODULE_1__.array(c)) {\n children = c;\n }\n else if (_is_js__WEBPACK_IMPORTED_MODULE_1__.primitive(c)) {\n text = c;\n }\n else if (c && c.sel) {\n children = [c];\n }\n }\n else if (b !== undefined && b !== null) {\n if (_is_js__WEBPACK_IMPORTED_MODULE_1__.array(b)) {\n children = b;\n }\n else if (_is_js__WEBPACK_IMPORTED_MODULE_1__.primitive(b)) {\n text = b;\n }\n else if (b && b.sel) {\n children = [b];\n }\n else {\n data = b;\n }\n }\n if (children !== undefined) {\n for (i = 0; i < children.length; ++i) {\n if (_is_js__WEBPACK_IMPORTED_MODULE_1__.primitive(children[i]))\n children[i] = (0,_vnode_js__WEBPACK_IMPORTED_MODULE_0__.vnode)(undefined, undefined, undefined, children[i], undefined);\n }\n }\n if (sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&\n (sel.length === 3 || sel[3] === '.' || sel[3] === '#')) {\n addNS(data, children, sel);\n }\n return (0,_vnode_js__WEBPACK_IMPORTED_MODULE_0__.vnode)(sel, data, children, text, undefined);\n}\n;\n//# sourceMappingURL=h.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9oLmpzP2M1OWMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQW1DO0FBQ0w7QUFDOUI7QUFDQTtBQUNBO0FBQ0EsdUJBQXVCLHFCQUFxQjtBQUM1QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVkseUNBQVE7QUFDcEI7QUFDQTtBQUNBLGlCQUFpQiw2Q0FBWTtBQUM3QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVkseUNBQVE7QUFDcEI7QUFDQTtBQUNBLGlCQUFpQiw2Q0FBWTtBQUM3QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFtQixxQkFBcUI7QUFDeEMsZ0JBQWdCLDZDQUFZO0FBQzVCLDhCQUE4QixnREFBSztBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLGdEQUFLO0FBQ2hCO0FBQ0E7QUFDQSIsImZpbGUiOiI2LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgdm5vZGUgfSBmcm9tIFwiLi92bm9kZS5qc1wiO1xuaW1wb3J0ICogYXMgaXMgZnJvbSBcIi4vaXMuanNcIjtcbmZ1bmN0aW9uIGFkZE5TKGRhdGEsIGNoaWxkcmVuLCBzZWwpIHtcbiAgICBkYXRhLm5zID0gJ2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJztcbiAgICBpZiAoc2VsICE9PSAnZm9yZWlnbk9iamVjdCcgJiYgY2hpbGRyZW4gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGNoaWxkcmVuLmxlbmd0aDsgKytpKSB7XG4gICAgICAgICAgICBjb25zdCBjaGlsZERhdGEgPSBjaGlsZHJlbltpXS5kYXRhO1xuICAgICAgICAgICAgaWYgKGNoaWxkRGF0YSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgYWRkTlMoY2hpbGREYXRhLCBjaGlsZHJlbltpXS5jaGlsZHJlbiwgY2hpbGRyZW5baV0uc2VsKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbn1cbmV4cG9ydCBmdW5jdGlvbiBoKHNlbCwgYiwgYykge1xuICAgIHZhciBkYXRhID0ge307XG4gICAgdmFyIGNoaWxkcmVuO1xuICAgIHZhciB0ZXh0O1xuICAgIHZhciBpO1xuICAgIGlmIChjICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgaWYgKGIgIT09IG51bGwpIHtcbiAgICAgICAgICAgIGRhdGEgPSBiO1xuICAgICAgICB9XG4gICAgICAgIGlmIChpcy5hcnJheShjKSkge1xuICAgICAgICAgICAgY2hpbGRyZW4gPSBjO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGlzLnByaW1pdGl2ZShjKSkge1xuICAgICAgICAgICAgdGV4dCA9IGM7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoYyAmJiBjLnNlbCkge1xuICAgICAgICAgICAgY2hpbGRyZW4gPSBbY107XG4gICAgICAgIH1cbiAgICB9XG4gICAgZWxzZSBpZiAoYiAhPT0gdW5kZWZpbmVkICYmIGIgIT09IG51bGwpIHtcbiAgICAgICAgaWYgKGlzLmFycmF5KGIpKSB7XG4gICAgICAgICAgICBjaGlsZHJlbiA9IGI7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoaXMucHJpbWl0aXZlKGIpKSB7XG4gICAgICAgICAgICB0ZXh0ID0gYjtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChiICYmIGIuc2VsKSB7XG4gICAgICAgICAgICBjaGlsZHJlbiA9IFtiXTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGRhdGEgPSBiO1xuICAgICAgICB9XG4gICAgfVxuICAgIGlmIChjaGlsZHJlbiAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBjaGlsZHJlbi5sZW5ndGg7ICsraSkge1xuICAgICAgICAgICAgaWYgKGlzLnByaW1pdGl2ZShjaGlsZHJlbltpXSkpXG4gICAgICAgICAgICAgICAgY2hpbGRyZW5baV0gPSB2bm9kZSh1bmRlZmluZWQsIHVuZGVmaW5lZCwgdW5kZWZpbmVkLCBjaGlsZHJlbltpXSwgdW5kZWZpbmVkKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBpZiAoc2VsWzBdID09PSAncycgJiYgc2VsWzFdID09PSAndicgJiYgc2VsWzJdID09PSAnZycgJiZcbiAgICAgICAgKHNlbC5sZW5ndGggPT09IDMgfHwgc2VsWzNdID09PSAnLicgfHwgc2VsWzNdID09PSAnIycpKSB7XG4gICAgICAgIGFkZE5TKGRhdGEsIGNoaWxkcmVuLCBzZWwpO1xuICAgIH1cbiAgICByZXR1cm4gdm5vZGUoc2VsLCBkYXRhLCBjaGlsZHJlbiwgdGV4dCwgdW5kZWZpbmVkKTtcbn1cbjtcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWguanMubWFwIl0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///6\n"); + +/***/ }), +/* 7 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"classModule\": () => (/* binding */ classModule)\n/* harmony export */ });\nfunction updateClass(oldVnode, vnode) {\n var cur;\n var name;\n var elm = vnode.elm;\n var oldClass = oldVnode.data.class;\n var klass = vnode.data.class;\n if (!oldClass && !klass)\n return;\n if (oldClass === klass)\n return;\n oldClass = oldClass || {};\n klass = klass || {};\n for (name in oldClass) {\n if (oldClass[name] &&\n !Object.prototype.hasOwnProperty.call(klass, name)) {\n // was `true` and now not provided\n elm.classList.remove(name);\n }\n }\n for (name in klass) {\n cur = klass[name];\n if (cur !== oldClass[name]) {\n elm.classList[cur ? 'add' : 'remove'](name);\n }\n }\n}\nconst classModule = { create: updateClass, update: updateClass };\n//# sourceMappingURL=class.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9tb2R1bGVzL2NsYXNzLmpzPzBmNzciXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDTyxxQkFBcUI7QUFDNUIiLCJmaWxlIjoiNy5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIHVwZGF0ZUNsYXNzKG9sZFZub2RlLCB2bm9kZSkge1xuICAgIHZhciBjdXI7XG4gICAgdmFyIG5hbWU7XG4gICAgdmFyIGVsbSA9IHZub2RlLmVsbTtcbiAgICB2YXIgb2xkQ2xhc3MgPSBvbGRWbm9kZS5kYXRhLmNsYXNzO1xuICAgIHZhciBrbGFzcyA9IHZub2RlLmRhdGEuY2xhc3M7XG4gICAgaWYgKCFvbGRDbGFzcyAmJiAha2xhc3MpXG4gICAgICAgIHJldHVybjtcbiAgICBpZiAob2xkQ2xhc3MgPT09IGtsYXNzKVxuICAgICAgICByZXR1cm47XG4gICAgb2xkQ2xhc3MgPSBvbGRDbGFzcyB8fCB7fTtcbiAgICBrbGFzcyA9IGtsYXNzIHx8IHt9O1xuICAgIGZvciAobmFtZSBpbiBvbGRDbGFzcykge1xuICAgICAgICBpZiAob2xkQ2xhc3NbbmFtZV0gJiZcbiAgICAgICAgICAgICFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoa2xhc3MsIG5hbWUpKSB7XG4gICAgICAgICAgICAvLyB3YXMgYHRydWVgIGFuZCBub3cgbm90IHByb3ZpZGVkXG4gICAgICAgICAgICBlbG0uY2xhc3NMaXN0LnJlbW92ZShuYW1lKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBmb3IgKG5hbWUgaW4ga2xhc3MpIHtcbiAgICAgICAgY3VyID0ga2xhc3NbbmFtZV07XG4gICAgICAgIGlmIChjdXIgIT09IG9sZENsYXNzW25hbWVdKSB7XG4gICAgICAgICAgICBlbG0uY2xhc3NMaXN0W2N1ciA/ICdhZGQnIDogJ3JlbW92ZSddKG5hbWUpO1xuICAgICAgICB9XG4gICAgfVxufVxuZXhwb3J0IGNvbnN0IGNsYXNzTW9kdWxlID0geyBjcmVhdGU6IHVwZGF0ZUNsYXNzLCB1cGRhdGU6IHVwZGF0ZUNsYXNzIH07XG4vLyMgc291cmNlTWFwcGluZ1VSTD1jbGFzcy5qcy5tYXAiXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///7\n"); + +/***/ }), +/* 8 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"datasetModule\": () => (/* binding */ datasetModule)\n/* harmony export */ });\nconst CAPS_REGEX = /[A-Z]/g;\nfunction updateDataset(oldVnode, vnode) {\n const elm = vnode.elm;\n let oldDataset = oldVnode.data.dataset;\n let dataset = vnode.data.dataset;\n let key;\n if (!oldDataset && !dataset)\n return;\n if (oldDataset === dataset)\n return;\n oldDataset = oldDataset || {};\n dataset = dataset || {};\n const d = elm.dataset;\n for (key in oldDataset) {\n if (!dataset[key]) {\n if (d) {\n if (key in d) {\n delete d[key];\n }\n }\n else {\n elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase());\n }\n }\n }\n for (key in dataset) {\n if (oldDataset[key] !== dataset[key]) {\n if (d) {\n d[key] = dataset[key];\n }\n else {\n elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key]);\n }\n }\n }\n}\nconst datasetModule = { create: updateDataset, update: updateDataset };\n//# sourceMappingURL=dataset.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9tb2R1bGVzL2RhdGFzZXQuanM/YjM3ZiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ08sdUJBQXVCO0FBQzlCIiwiZmlsZSI6IjguanMiLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBDQVBTX1JFR0VYID0gL1tBLVpdL2c7XG5mdW5jdGlvbiB1cGRhdGVEYXRhc2V0KG9sZFZub2RlLCB2bm9kZSkge1xuICAgIGNvbnN0IGVsbSA9IHZub2RlLmVsbTtcbiAgICBsZXQgb2xkRGF0YXNldCA9IG9sZFZub2RlLmRhdGEuZGF0YXNldDtcbiAgICBsZXQgZGF0YXNldCA9IHZub2RlLmRhdGEuZGF0YXNldDtcbiAgICBsZXQga2V5O1xuICAgIGlmICghb2xkRGF0YXNldCAmJiAhZGF0YXNldClcbiAgICAgICAgcmV0dXJuO1xuICAgIGlmIChvbGREYXRhc2V0ID09PSBkYXRhc2V0KVxuICAgICAgICByZXR1cm47XG4gICAgb2xkRGF0YXNldCA9IG9sZERhdGFzZXQgfHwge307XG4gICAgZGF0YXNldCA9IGRhdGFzZXQgfHwge307XG4gICAgY29uc3QgZCA9IGVsbS5kYXRhc2V0O1xuICAgIGZvciAoa2V5IGluIG9sZERhdGFzZXQpIHtcbiAgICAgICAgaWYgKCFkYXRhc2V0W2tleV0pIHtcbiAgICAgICAgICAgIGlmIChkKSB7XG4gICAgICAgICAgICAgICAgaWYgKGtleSBpbiBkKSB7XG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBkW2tleV07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZWxtLnJlbW92ZUF0dHJpYnV0ZSgnZGF0YS0nICsga2V5LnJlcGxhY2UoQ0FQU19SRUdFWCwgJy0kJicpLnRvTG93ZXJDYXNlKCkpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGZvciAoa2V5IGluIGRhdGFzZXQpIHtcbiAgICAgICAgaWYgKG9sZERhdGFzZXRba2V5XSAhPT0gZGF0YXNldFtrZXldKSB7XG4gICAgICAgICAgICBpZiAoZCkge1xuICAgICAgICAgICAgICAgIGRba2V5XSA9IGRhdGFzZXRba2V5XTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGVsbS5zZXRBdHRyaWJ1dGUoJ2RhdGEtJyArIGtleS5yZXBsYWNlKENBUFNfUkVHRVgsICctJCYnKS50b0xvd2VyQ2FzZSgpLCBkYXRhc2V0W2tleV0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxufVxuZXhwb3J0IGNvbnN0IGRhdGFzZXRNb2R1bGUgPSB7IGNyZWF0ZTogdXBkYXRlRGF0YXNldCwgdXBkYXRlOiB1cGRhdGVEYXRhc2V0IH07XG4vLyMgc291cmNlTWFwcGluZ1VSTD1kYXRhc2V0LmpzLm1hcCJdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///8\n"); + +/***/ }), +/* 9 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"eventListenersModule\": () => (/* binding */ eventListenersModule)\n/* harmony export */ });\nfunction invokeHandler(handler, vnode, event) {\n if (typeof handler === 'function') {\n // call function handler\n handler.call(vnode, event, vnode);\n }\n else if (typeof handler === 'object') {\n // call multiple handlers\n for (var i = 0; i < handler.length; i++) {\n invokeHandler(handler[i], vnode, event);\n }\n }\n}\nfunction handleEvent(event, vnode) {\n var name = event.type;\n var on = vnode.data.on;\n // call event handler(s) if exists\n if (on && on[name]) {\n invokeHandler(on[name], vnode, event);\n }\n}\nfunction createListener() {\n return function handler(event) {\n handleEvent(event, handler.vnode);\n };\n}\nfunction updateEventListeners(oldVnode, vnode) {\n var oldOn = oldVnode.data.on;\n var oldListener = oldVnode.listener;\n var oldElm = oldVnode.elm;\n var on = vnode && vnode.data.on;\n var elm = (vnode && vnode.elm);\n var name;\n // optimization for reused immutable handlers\n if (oldOn === on) {\n return;\n }\n // remove existing listeners which no longer used\n if (oldOn && oldListener) {\n // if element changed or deleted we remove all existing listeners unconditionally\n if (!on) {\n for (name in oldOn) {\n // remove listener if element was changed or existing listeners removed\n oldElm.removeEventListener(name, oldListener, false);\n }\n }\n else {\n for (name in oldOn) {\n // remove listener if existing listener removed\n if (!on[name]) {\n oldElm.removeEventListener(name, oldListener, false);\n }\n }\n }\n }\n // add new listeners which has not already attached\n if (on) {\n // reuse existing listener or create new\n var listener = vnode.listener = oldVnode.listener || createListener();\n // update vnode for listener\n listener.vnode = vnode;\n // if element changed or added we add all needed listeners unconditionally\n if (!oldOn) {\n for (name in on) {\n // add listener if element was changed or new listeners added\n elm.addEventListener(name, listener, false);\n }\n }\n else {\n for (name in on) {\n // add listener if new listener added\n if (!oldOn[name]) {\n elm.addEventListener(name, listener, false);\n }\n }\n }\n }\n}\nconst eventListenersModule = {\n create: updateEventListeners,\n update: updateEventListeners,\n destroy: updateEventListeners\n};\n//# sourceMappingURL=eventlisteners.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9tb2R1bGVzL2V2ZW50bGlzdGVuZXJzLmpzP2JjZWMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUJBQXVCLG9CQUFvQjtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiOS5qcyIsInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIGludm9rZUhhbmRsZXIoaGFuZGxlciwgdm5vZGUsIGV2ZW50KSB7XG4gICAgaWYgKHR5cGVvZiBoYW5kbGVyID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIC8vIGNhbGwgZnVuY3Rpb24gaGFuZGxlclxuICAgICAgICBoYW5kbGVyLmNhbGwodm5vZGUsIGV2ZW50LCB2bm9kZSk7XG4gICAgfVxuICAgIGVsc2UgaWYgKHR5cGVvZiBoYW5kbGVyID09PSAnb2JqZWN0Jykge1xuICAgICAgICAvLyBjYWxsIG11bHRpcGxlIGhhbmRsZXJzXG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgaGFuZGxlci5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaW52b2tlSGFuZGxlcihoYW5kbGVyW2ldLCB2bm9kZSwgZXZlbnQpO1xuICAgICAgICB9XG4gICAgfVxufVxuZnVuY3Rpb24gaGFuZGxlRXZlbnQoZXZlbnQsIHZub2RlKSB7XG4gICAgdmFyIG5hbWUgPSBldmVudC50eXBlO1xuICAgIHZhciBvbiA9IHZub2RlLmRhdGEub247XG4gICAgLy8gY2FsbCBldmVudCBoYW5kbGVyKHMpIGlmIGV4aXN0c1xuICAgIGlmIChvbiAmJiBvbltuYW1lXSkge1xuICAgICAgICBpbnZva2VIYW5kbGVyKG9uW25hbWVdLCB2bm9kZSwgZXZlbnQpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGNyZWF0ZUxpc3RlbmVyKCkge1xuICAgIHJldHVybiBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50KSB7XG4gICAgICAgIGhhbmRsZUV2ZW50KGV2ZW50LCBoYW5kbGVyLnZub2RlKTtcbiAgICB9O1xufVxuZnVuY3Rpb24gdXBkYXRlRXZlbnRMaXN0ZW5lcnMob2xkVm5vZGUsIHZub2RlKSB7XG4gICAgdmFyIG9sZE9uID0gb2xkVm5vZGUuZGF0YS5vbjtcbiAgICB2YXIgb2xkTGlzdGVuZXIgPSBvbGRWbm9kZS5saXN0ZW5lcjtcbiAgICB2YXIgb2xkRWxtID0gb2xkVm5vZGUuZWxtO1xuICAgIHZhciBvbiA9IHZub2RlICYmIHZub2RlLmRhdGEub247XG4gICAgdmFyIGVsbSA9ICh2bm9kZSAmJiB2bm9kZS5lbG0pO1xuICAgIHZhciBuYW1lO1xuICAgIC8vIG9wdGltaXphdGlvbiBmb3IgcmV1c2VkIGltbXV0YWJsZSBoYW5kbGVyc1xuICAgIGlmIChvbGRPbiA9PT0gb24pIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyByZW1vdmUgZXhpc3RpbmcgbGlzdGVuZXJzIHdoaWNoIG5vIGxvbmdlciB1c2VkXG4gICAgaWYgKG9sZE9uICYmIG9sZExpc3RlbmVyKSB7XG4gICAgICAgIC8vIGlmIGVsZW1lbnQgY2hhbmdlZCBvciBkZWxldGVkIHdlIHJlbW92ZSBhbGwgZXhpc3RpbmcgbGlzdGVuZXJzIHVuY29uZGl0aW9uYWxseVxuICAgICAgICBpZiAoIW9uKSB7XG4gICAgICAgICAgICBmb3IgKG5hbWUgaW4gb2xkT24pIHtcbiAgICAgICAgICAgICAgICAvLyByZW1vdmUgbGlzdGVuZXIgaWYgZWxlbWVudCB3YXMgY2hhbmdlZCBvciBleGlzdGluZyBsaXN0ZW5lcnMgcmVtb3ZlZFxuICAgICAgICAgICAgICAgIG9sZEVsbS5yZW1vdmVFdmVudExpc3RlbmVyKG5hbWUsIG9sZExpc3RlbmVyLCBmYWxzZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBmb3IgKG5hbWUgaW4gb2xkT24pIHtcbiAgICAgICAgICAgICAgICAvLyByZW1vdmUgbGlzdGVuZXIgaWYgZXhpc3RpbmcgbGlzdGVuZXIgcmVtb3ZlZFxuICAgICAgICAgICAgICAgIGlmICghb25bbmFtZV0pIHtcbiAgICAgICAgICAgICAgICAgICAgb2xkRWxtLnJlbW92ZUV2ZW50TGlzdGVuZXIobmFtZSwgb2xkTGlzdGVuZXIsIGZhbHNlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgLy8gYWRkIG5ldyBsaXN0ZW5lcnMgd2hpY2ggaGFzIG5vdCBhbHJlYWR5IGF0dGFjaGVkXG4gICAgaWYgKG9uKSB7XG4gICAgICAgIC8vIHJldXNlIGV4aXN0aW5nIGxpc3RlbmVyIG9yIGNyZWF0ZSBuZXdcbiAgICAgICAgdmFyIGxpc3RlbmVyID0gdm5vZGUubGlzdGVuZXIgPSBvbGRWbm9kZS5saXN0ZW5lciB8fCBjcmVhdGVMaXN0ZW5lcigpO1xuICAgICAgICAvLyB1cGRhdGUgdm5vZGUgZm9yIGxpc3RlbmVyXG4gICAgICAgIGxpc3RlbmVyLnZub2RlID0gdm5vZGU7XG4gICAgICAgIC8vIGlmIGVsZW1lbnQgY2hhbmdlZCBvciBhZGRlZCB3ZSBhZGQgYWxsIG5lZWRlZCBsaXN0ZW5lcnMgdW5jb25kaXRpb25hbGx5XG4gICAgICAgIGlmICghb2xkT24pIHtcbiAgICAgICAgICAgIGZvciAobmFtZSBpbiBvbikge1xuICAgICAgICAgICAgICAgIC8vIGFkZCBsaXN0ZW5lciBpZiBlbGVtZW50IHdhcyBjaGFuZ2VkIG9yIG5ldyBsaXN0ZW5lcnMgYWRkZWRcbiAgICAgICAgICAgICAgICBlbG0uYWRkRXZlbnRMaXN0ZW5lcihuYW1lLCBsaXN0ZW5lciwgZmFsc2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgZm9yIChuYW1lIGluIG9uKSB7XG4gICAgICAgICAgICAgICAgLy8gYWRkIGxpc3RlbmVyIGlmIG5ldyBsaXN0ZW5lciBhZGRlZFxuICAgICAgICAgICAgICAgIGlmICghb2xkT25bbmFtZV0pIHtcbiAgICAgICAgICAgICAgICAgICAgZWxtLmFkZEV2ZW50TGlzdGVuZXIobmFtZSwgbGlzdGVuZXIsIGZhbHNlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5leHBvcnQgY29uc3QgZXZlbnRMaXN0ZW5lcnNNb2R1bGUgPSB7XG4gICAgY3JlYXRlOiB1cGRhdGVFdmVudExpc3RlbmVycyxcbiAgICB1cGRhdGU6IHVwZGF0ZUV2ZW50TGlzdGVuZXJzLFxuICAgIGRlc3Ryb3k6IHVwZGF0ZUV2ZW50TGlzdGVuZXJzXG59O1xuLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZXZlbnRsaXN0ZW5lcnMuanMubWFwIl0sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///9\n"); + +/***/ }), +/* 10 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"styleModule\": () => (/* binding */ styleModule)\n/* harmony export */ });\n// Bindig `requestAnimationFrame` like this fixes a bug in IE/Edge. See #360 and #409.\nvar raf = (typeof window !== 'undefined' && (window.requestAnimationFrame).bind(window)) || setTimeout;\nvar nextFrame = function (fn) {\n raf(function () {\n raf(fn);\n });\n};\nvar reflowForced = false;\nfunction setNextFrame(obj, prop, val) {\n nextFrame(function () {\n obj[prop] = val;\n });\n}\nfunction updateStyle(oldVnode, vnode) {\n var cur;\n var name;\n var elm = vnode.elm;\n var oldStyle = oldVnode.data.style;\n var style = vnode.data.style;\n if (!oldStyle && !style)\n return;\n if (oldStyle === style)\n return;\n oldStyle = oldStyle || {};\n style = style || {};\n var oldHasDel = 'delayed' in oldStyle;\n for (name in oldStyle) {\n if (!style[name]) {\n if (name[0] === '-' && name[1] === '-') {\n elm.style.removeProperty(name);\n }\n else {\n elm.style[name] = '';\n }\n }\n }\n for (name in style) {\n cur = style[name];\n if (name === 'delayed' && style.delayed) {\n for (const name2 in style.delayed) {\n cur = style.delayed[name2];\n if (!oldHasDel || cur !== oldStyle.delayed[name2]) {\n setNextFrame(elm.style, name2, cur);\n }\n }\n }\n else if (name !== 'remove' && cur !== oldStyle[name]) {\n if (name[0] === '-' && name[1] === '-') {\n elm.style.setProperty(name, cur);\n }\n else {\n elm.style[name] = cur;\n }\n }\n }\n}\nfunction applyDestroyStyle(vnode) {\n var style;\n var name;\n var elm = vnode.elm;\n var s = vnode.data.style;\n if (!s || !(style = s.destroy))\n return;\n for (name in style) {\n elm.style[name] = style[name];\n }\n}\nfunction applyRemoveStyle(vnode, rm) {\n var s = vnode.data.style;\n if (!s || !s.remove) {\n rm();\n return;\n }\n if (!reflowForced) {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n vnode.elm.offsetLeft;\n reflowForced = true;\n }\n var name;\n var elm = vnode.elm;\n var i = 0;\n var compStyle;\n var style = s.remove;\n var amount = 0;\n var applied = [];\n for (name in style) {\n applied.push(name);\n elm.style[name] = style[name];\n }\n compStyle = getComputedStyle(elm);\n var props = compStyle['transition-property'].split(', ');\n for (; i < props.length; ++i) {\n if (applied.indexOf(props[i]) !== -1)\n amount++;\n }\n elm.addEventListener('transitionend', function (ev) {\n if (ev.target === elm)\n --amount;\n if (amount === 0)\n rm();\n });\n}\nfunction forceReflow() {\n reflowForced = false;\n}\nconst styleModule = {\n pre: forceReflow,\n create: updateStyle,\n update: updateStyle,\n destroy: applyDestroyStyle,\n remove: applyRemoveStyle\n};\n//# sourceMappingURL=style.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvc25hYmJkb20vYnVpbGQvcGFja2FnZS9tb2R1bGVzL3N0eWxlLmpzP2ZjMDkiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVUsa0JBQWtCO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6IjEwLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQmluZGlnIGByZXF1ZXN0QW5pbWF0aW9uRnJhbWVgIGxpa2UgdGhpcyBmaXhlcyBhIGJ1ZyBpbiBJRS9FZGdlLiBTZWUgIzM2MCBhbmQgIzQwOS5cbnZhciByYWYgPSAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgKHdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUpLmJpbmQod2luZG93KSkgfHwgc2V0VGltZW91dDtcbnZhciBuZXh0RnJhbWUgPSBmdW5jdGlvbiAoZm4pIHtcbiAgICByYWYoZnVuY3Rpb24gKCkge1xuICAgICAgICByYWYoZm4pO1xuICAgIH0pO1xufTtcbnZhciByZWZsb3dGb3JjZWQgPSBmYWxzZTtcbmZ1bmN0aW9uIHNldE5leHRGcmFtZShvYmosIHByb3AsIHZhbCkge1xuICAgIG5leHRGcmFtZShmdW5jdGlvbiAoKSB7XG4gICAgICAgIG9ialtwcm9wXSA9IHZhbDtcbiAgICB9KTtcbn1cbmZ1bmN0aW9uIHVwZGF0ZVN0eWxlKG9sZFZub2RlLCB2bm9kZSkge1xuICAgIHZhciBjdXI7XG4gICAgdmFyIG5hbWU7XG4gICAgdmFyIGVsbSA9IHZub2RlLmVsbTtcbiAgICB2YXIgb2xkU3R5bGUgPSBvbGRWbm9kZS5kYXRhLnN0eWxlO1xuICAgIHZhciBzdHlsZSA9IHZub2RlLmRhdGEuc3R5bGU7XG4gICAgaWYgKCFvbGRTdHlsZSAmJiAhc3R5bGUpXG4gICAgICAgIHJldHVybjtcbiAgICBpZiAob2xkU3R5bGUgPT09IHN0eWxlKVxuICAgICAgICByZXR1cm47XG4gICAgb2xkU3R5bGUgPSBvbGRTdHlsZSB8fCB7fTtcbiAgICBzdHlsZSA9IHN0eWxlIHx8IHt9O1xuICAgIHZhciBvbGRIYXNEZWwgPSAnZGVsYXllZCcgaW4gb2xkU3R5bGU7XG4gICAgZm9yIChuYW1lIGluIG9sZFN0eWxlKSB7XG4gICAgICAgIGlmICghc3R5bGVbbmFtZV0pIHtcbiAgICAgICAgICAgIGlmIChuYW1lWzBdID09PSAnLScgJiYgbmFtZVsxXSA9PT0gJy0nKSB7XG4gICAgICAgICAgICAgICAgZWxtLnN0eWxlLnJlbW92ZVByb3BlcnR5KG5hbWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZWxtLnN0eWxlW25hbWVdID0gJyc7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgZm9yIChuYW1lIGluIHN0eWxlKSB7XG4gICAgICAgIGN1ciA9IHN0eWxlW25hbWVdO1xuICAgICAgICBpZiAobmFtZSA9PT0gJ2RlbGF5ZWQnICYmIHN0eWxlLmRlbGF5ZWQpIHtcbiAgICAgICAgICAgIGZvciAoY29uc3QgbmFtZTIgaW4gc3R5bGUuZGVsYXllZCkge1xuICAgICAgICAgICAgICAgIGN1ciA9IHN0eWxlLmRlbGF5ZWRbbmFtZTJdO1xuICAgICAgICAgICAgICAgIGlmICghb2xkSGFzRGVsIHx8IGN1ciAhPT0gb2xkU3R5bGUuZGVsYXllZFtuYW1lMl0pIHtcbiAgICAgICAgICAgICAgICAgICAgc2V0TmV4dEZyYW1lKGVsbS5zdHlsZSwgbmFtZTIsIGN1cik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKG5hbWUgIT09ICdyZW1vdmUnICYmIGN1ciAhPT0gb2xkU3R5bGVbbmFtZV0pIHtcbiAgICAgICAgICAgIGlmIChuYW1lWzBdID09PSAnLScgJiYgbmFtZVsxXSA9PT0gJy0nKSB7XG4gICAgICAgICAgICAgICAgZWxtLnN0eWxlLnNldFByb3BlcnR5KG5hbWUsIGN1cik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICBlbG0uc3R5bGVbbmFtZV0gPSBjdXI7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5mdW5jdGlvbiBhcHBseURlc3Ryb3lTdHlsZSh2bm9kZSkge1xuICAgIHZhciBzdHlsZTtcbiAgICB2YXIgbmFtZTtcbiAgICB2YXIgZWxtID0gdm5vZGUuZWxtO1xuICAgIHZhciBzID0gdm5vZGUuZGF0YS5zdHlsZTtcbiAgICBpZiAoIXMgfHwgIShzdHlsZSA9IHMuZGVzdHJveSkpXG4gICAgICAgIHJldHVybjtcbiAgICBmb3IgKG5hbWUgaW4gc3R5bGUpIHtcbiAgICAgICAgZWxtLnN0eWxlW25hbWVdID0gc3R5bGVbbmFtZV07XG4gICAgfVxufVxuZnVuY3Rpb24gYXBwbHlSZW1vdmVTdHlsZSh2bm9kZSwgcm0pIHtcbiAgICB2YXIgcyA9IHZub2RlLmRhdGEuc3R5bGU7XG4gICAgaWYgKCFzIHx8ICFzLnJlbW92ZSkge1xuICAgICAgICBybSgpO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICghcmVmbG93Rm9yY2VkKSB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdW51c2VkLWV4cHJlc3Npb25zXG4gICAgICAgIHZub2RlLmVsbS5vZmZzZXRMZWZ0O1xuICAgICAgICByZWZsb3dGb3JjZWQgPSB0cnVlO1xuICAgIH1cbiAgICB2YXIgbmFtZTtcbiAgICB2YXIgZWxtID0gdm5vZGUuZWxtO1xuICAgIHZhciBpID0gMDtcbiAgICB2YXIgY29tcFN0eWxlO1xuICAgIHZhciBzdHlsZSA9IHMucmVtb3ZlO1xuICAgIHZhciBhbW91bnQgPSAwO1xuICAgIHZhciBhcHBsaWVkID0gW107XG4gICAgZm9yIChuYW1lIGluIHN0eWxlKSB7XG4gICAgICAgIGFwcGxpZWQucHVzaChuYW1lKTtcbiAgICAgICAgZWxtLnN0eWxlW25hbWVdID0gc3R5bGVbbmFtZV07XG4gICAgfVxuICAgIGNvbXBTdHlsZSA9IGdldENvbXB1dGVkU3R5bGUoZWxtKTtcbiAgICB2YXIgcHJvcHMgPSBjb21wU3R5bGVbJ3RyYW5zaXRpb24tcHJvcGVydHknXS5zcGxpdCgnLCAnKTtcbiAgICBmb3IgKDsgaSA8IHByb3BzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgIGlmIChhcHBsaWVkLmluZGV4T2YocHJvcHNbaV0pICE9PSAtMSlcbiAgICAgICAgICAgIGFtb3VudCsrO1xuICAgIH1cbiAgICBlbG0uYWRkRXZlbnRMaXN0ZW5lcigndHJhbnNpdGlvbmVuZCcsIGZ1bmN0aW9uIChldikge1xuICAgICAgICBpZiAoZXYudGFyZ2V0ID09PSBlbG0pXG4gICAgICAgICAgICAtLWFtb3VudDtcbiAgICAgICAgaWYgKGFtb3VudCA9PT0gMClcbiAgICAgICAgICAgIHJtKCk7XG4gICAgfSk7XG59XG5mdW5jdGlvbiBmb3JjZVJlZmxvdygpIHtcbiAgICByZWZsb3dGb3JjZWQgPSBmYWxzZTtcbn1cbmV4cG9ydCBjb25zdCBzdHlsZU1vZHVsZSA9IHtcbiAgICBwcmU6IGZvcmNlUmVmbG93LFxuICAgIGNyZWF0ZTogdXBkYXRlU3R5bGUsXG4gICAgdXBkYXRlOiB1cGRhdGVTdHlsZSxcbiAgICBkZXN0cm95OiBhcHBseURlc3Ryb3lTdHlsZSxcbiAgICByZW1vdmU6IGFwcGx5UmVtb3ZlU3R5bGVcbn07XG4vLyMgc291cmNlTWFwcGluZ1VSTD1zdHlsZS5qcy5tYXAiXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///10\n"); + +/***/ }), +/* 11 */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"identity\": () => (/* binding */ identity),\n/* harmony export */ \"isNil\": () => (/* binding */ isNil),\n/* harmony export */ \"isBoolean\": () => (/* binding */ isBoolean),\n/* harmony export */ \"isInteger\": () => (/* binding */ isInteger),\n/* harmony export */ \"isNumber\": () => (/* binding */ isNumber),\n/* harmony export */ \"isString\": () => (/* binding */ isString),\n/* harmony export */ \"isArray\": () => (/* binding */ isArray),\n/* harmony export */ \"isObject\": () => (/* binding */ isObject),\n/* harmony export */ \"strictParseInt\": () => (/* binding */ strictParseInt),\n/* harmony export */ \"strictParseFloat\": () => (/* binding */ strictParseFloat),\n/* harmony export */ \"clone\": () => (/* binding */ clone),\n/* harmony export */ \"zip\": () => (/* binding */ zip),\n/* harmony export */ \"toPairs\": () => (/* binding */ toPairs),\n/* harmony export */ \"fromPairs\": () => (/* binding */ fromPairs),\n/* harmony export */ \"flatten\": () => (/* binding */ flatten),\n/* harmony export */ \"pipe\": () => (/* binding */ pipe),\n/* harmony export */ \"flap\": () => (/* binding */ flap),\n/* harmony export */ \"curry\": () => (/* binding */ curry),\n/* harmony export */ \"equals\": () => (/* binding */ equals),\n/* harmony export */ \"repeat\": () => (/* binding */ repeat),\n/* harmony export */ \"get\": () => (/* binding */ get),\n/* harmony export */ \"change\": () => (/* binding */ change),\n/* harmony export */ \"set\": () => (/* binding */ set),\n/* harmony export */ \"omit\": () => (/* binding */ omit),\n/* harmony export */ \"move\": () => (/* binding */ move),\n/* harmony export */ \"sort\": () => (/* binding */ sort),\n/* harmony export */ \"sortBy\": () => (/* binding */ sortBy),\n/* harmony export */ \"pick\": () => (/* binding */ pick),\n/* harmony export */ \"map\": () => (/* binding */ map),\n/* harmony export */ \"filter\": () => (/* binding */ filter),\n/* harmony export */ \"append\": () => (/* binding */ append),\n/* harmony export */ \"reduce\": () => (/* binding */ reduce),\n/* harmony export */ \"merge\": () => (/* binding */ merge),\n/* harmony export */ \"mergeAll\": () => (/* binding */ mergeAll),\n/* harmony export */ \"find\": () => (/* binding */ find),\n/* harmony export */ \"findIndex\": () => (/* binding */ findIndex),\n/* harmony export */ \"concat\": () => (/* binding */ concat),\n/* harmony export */ \"union\": () => (/* binding */ union),\n/* harmony export */ \"contains\": () => (/* binding */ contains),\n/* harmony export */ \"insert\": () => (/* binding */ insert),\n/* harmony export */ \"slice\": () => (/* binding */ slice),\n/* harmony export */ \"reverse\": () => (/* binding */ reverse),\n/* harmony export */ \"length\": () => (/* binding */ length),\n/* harmony export */ \"inc\": () => (/* binding */ inc),\n/* harmony export */ \"dec\": () => (/* binding */ dec),\n/* harmony export */ \"not\": () => (/* binding */ not),\n/* harmony export */ \"sleep\": () => (/* binding */ sleep),\n/* harmony export */ \"delay\": () => (/* binding */ delay)\n/* harmony export */ });\n/**\n * Utility library for manipulation of JSON data.\n *\n * Main characteristics:\n * - input/output data types are limited to JSON data, functions and\n * `undefined` (sparse arrays and complex objects with prototype chain are\n * not supported)\n * - functional API with curried functions (similar to ramdajs)\n * - implementation based on natively supported browser JS API\n * - scope limited to most used functions in hat projects\n * - usage of `paths` instead of `lenses`\n *\n * TODO: define convetion for naming arguments based on their type and\n * semantics\n *\n * @module @hat-core/util\n */\n\n/**\n * Path can be an object property name, array index, or array of Paths\n *\n * TODO: explain paths and path compositions (include examples)\n *\n * @typedef {(String|Number|Path[])} module:@hat-core/util.Path\n */\n\n/**\n * Identity function returning same value provided as argument.\n *\n * @function\n * @sig a -> a\n * @param {*} x input value\n * @return {*} same value as input\n */\nconst identity = x => x;\n\n/**\n * Check if value is `null` or `undefined`.\n *\n * For same argument, if this function returns `true`, functions `isBoolean`,\n * `isInteger`, `isNumber`, `isString`, `isArray` and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nconst isNil = x => x == null;\n\n/**\n * Check if value is Boolean.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isInteger`, `isNumber`, `isString`, `isArray` and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nconst isBoolean = x => typeof(x) == 'boolean';\n\n/**\n * Check if value is Integer.\n *\n * For same argument, if this function returns `true`, function `isNumber` will\n * also return `true`.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isString`, `isArray` and `isObject` will return `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @type {Boolean}\n */\nconst isInteger = Number.isInteger;\n\n/**\n * Check if value is Number.\n *\n * For same argument, if this function returns `true`, function `isInteger` may\n * also return `true` if argument is integer number.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isString`, `isArray` and `isObject` will return `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nconst isNumber = x => typeof(x) == 'number';\n\n/**\n * Check if value is String.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isInteger`, `isNumber`, `isArray`, and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {Any} x input value\n * @type {Boolean}\n */\nconst isString = x => typeof(x) == 'string';\n\n/**\n * Check if value is Array.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isInteger`, `isNumber`, `isString`, and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nconst isArray = Array.isArray;\n\n/**\n * Check if value is Object.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isInteger`, `isNumber`, `isString`, and `isArray` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nconst isObject = x => typeof(x) == 'object' &&\n !isArray(x) &&\n !isNil(x);\n\n/**\n * Strictly parse integer from string\n *\n * If provided string doesn't represent integer value, `NaN` is returned.\n *\n * @function\n * @sig String -> Number\n * @param {String} value\n * @return {Number}\n */\nfunction strictParseInt(value) {\n if (/^(-|\\+)?([0-9]+)$/.test(value))\n return Number(value);\n return NaN;\n}\n\n/**\n * Strictly parse floating point number from string\n *\n * If provided string doesn't represent valid number, `NaN` is returned.\n *\n * @function\n * @sig String -> Number\n * @param {String} value\n * @return {Number}\n */\nfunction strictParseFloat(value) {\n if (/^(-|\\+)?([0-9]+(\\.[0-9]+)?)$/.test(value))\n return Number(value);\n return NaN;\n}\n\n/**\n * Create new deep copy of input value.\n *\n * In case of Objects or Arrays, new instances are created with elements\n * obtained by recursivly calling `clone` in input argument values.\n *\n * @function\n * @sig * -> *\n * @param {*} x value\n * @return {*} copy of value\n */\nfunction clone(x) {\n if (isArray(x))\n return Array.from(x, clone);\n if (isObject(x)) {\n let ret = {};\n for (let i in x)\n ret[i] = clone(x[i]);\n return ret;\n }\n return x;\n}\n\n/**\n * Combine two arrays in single array of pairs\n *\n * The returned array is truncated to the length of the shorter of the two\n * input arrays.\n *\n * @function\n * @sig [a] -> [b] -> [[a,b]]\n * @param {Array} arr1\n * @param {Array} arr2\n * @return {Array}\n */\nfunction zip(arr1, arr2) {\n return Array.from((function*() {\n for (let i = 0; i < arr1.length || i < arr2.length; ++i)\n yield [arr1[i], arr2[i]];\n })());\n}\n\n/**\n * Convert object to array of key, value pairs\n *\n * @function\n * @sig Object -> [[String,*]]\n * @param {Object} obj\n * @return {Array}\n */\nfunction toPairs(obj) {\n return Object.entries(obj);\n}\n\n/**\n * Convert array of key, value pairs to object\n *\n * @function\n * @sig [[String,*]] -> Object\n * @param {Array} arr\n * @return {Object}\n */\nfunction fromPairs(arr) {\n let ret = {};\n for (let [k, v] of arr)\n ret[k] = v;\n return ret;\n}\n\n/**\n * Flatten nested arrays.\n *\n * Create array with same elements as in input array where all elements which\n * are also arrays are replaced with elements of resulting recursive\n * application of flatten function.\n *\n * @function\n * @sig [a] -> [b]\n * @param {Array} arr\n * @return {Array}\n */\nfunction flatten(arr) {\n return Array.from((function* flatten(x) {\n if (isArray(x)) {\n for (let i of x)\n yield* flatten(i);\n } else {\n yield x;\n }\n })(arr));\n}\n\n/**\n * Pipe function calls\n *\n * Pipe provides functional composition with reversed order. First function\n * may have any arity and all other functions are called with only single\n * argument (result from previous function application).\n *\n * In case when no function is provided, pipe returns identity function.\n *\n * @function\n * @sig (((a1, a2, ..., an) -> b1), (b1 -> b2), ..., (bm1 -> bm)) -> ((a1, a2, ..., an) -> bm)\n * @param {...Function} fns functions\n * @return {Function}\n */\nfunction pipe(...fns) {\n if (fns.length < 1)\n return identity;\n return function (...args) {\n let ret = fns[0].apply(this, args);\n for (let fn of fns.slice(1))\n ret = fn(ret);\n return ret;\n };\n}\n\n/**\n * Apply list of functions to same arguments and return list of results\n *\n * @function\n * @sig ((a1 -> ... -> an -> b1), ..., (a1 -> ... -> an -> bm)) -> (a1 -> ... -> an -> [b1,...,bm])\n * @param {...Function} fns functions\n * @return {Function}\n */\nfunction flap(...fns) {\n return (...args) => fns.map(fn => fn.apply(this, args));\n}\n\n/**\n * Curry function with fixed arguments lenth\n *\n * Function arity is determined based on function's length property.\n *\n * @function\n * @sig (* -> a) -> (* -> a)\n * @param {Function} fn\n * @return {Function}\n */\nfunction curry(fn) {\n let wrapper = function(oldArgs) {\n return function(...args) {\n args = oldArgs.concat(args);\n if (args.length >= fn.length)\n return fn(...args);\n return wrapper(args);\n };\n };\n return wrapper([]);\n}\n\n/**\n * Deep object equality\n * (curried function)\n *\n * @function\n * @sig a -> b -> Boolean\n * @param {*} x\n * @param {*} y\n * @return {Boolean}\n */\nconst equals = curry((x, y) => {\n if (x === y)\n return true;\n if (typeof(x) != 'object' ||\n typeof(y) != 'object' ||\n x === null ||\n y === null)\n return false;\n if (Array.isArray(x) && Array.isArray(y)) {\n if (x.length != y.length)\n return false;\n for (let [a, b] of zip(x, y)) {\n if (!equals(a, b))\n return false;\n }\n return true;\n } else if (!Array.isArray(x) && !Array.isArray(y)) {\n if (Object.keys(x).length != Object.keys(y).length)\n return false;\n for (let key in x) {\n if (!(key in y))\n return false;\n }\n for (let key in x) {\n if (!equals(x[key], y[key]))\n return false;\n }\n return true;\n }\n return false;\n});\n\n\n/**\n * Create array by repeating same value\n * (curried function)\n *\n * @function\n * @sig a -> Number -> [a]\n * @param {*} x\n * @param {Number} n\n * @return {Array}\n */\nconst repeat = curry((x, n) => Array.from({length: n}, _ => x));\n\n/**\n * Get value referenced by path\n * (curried function)\n *\n * If input value doesn't contain provided path value, `undefined` is returned.\n *\n * @function\n * @sig Path -> a -> b\n * @param {Path} path\n * @param {*} x\n * @return {*}\n */\nconst get = curry((path, x) => {\n let ret = x;\n for (let i of flatten(path)) {\n if (ret === null || typeof(ret) != 'object')\n return undefined;\n ret = ret[i];\n }\n return ret;\n});\n\n/**\n * Change value referenced with path by appling function\n * (curried function)\n *\n * @function\n * @sig Path -> (a -> b) -> c -> c\n * @param {Path} path\n * @param {Function} fn\n * @param {*} x\n * @return {*}\n */\nconst change = curry((path, fn, x) => {\n return (function change(path, x) {\n if (path.length < 1)\n return fn(x);\n const [first, ...rest] = path;\n if (isInteger(first)) {\n x = (isArray(x) ? Array.from(x) : repeat(undefined, first));\n } else if (isString(first)) {\n x = (isObject(x) ? Object.assign({}, x) : {});\n } else {\n throw 'invalid path';\n }\n x[first] = change(rest, x[first]);\n return x;\n })(flatten(path), x);\n});\n\n/**\n * Replace value referenced with path with another value\n * (curried function)\n *\n * @function\n * @sig Path -> (a -> b) -> c -> c\n * @param {Path} path\n * @param {*} val\n * @param {*} x\n * @return {*}\n */\nconst set = curry((path, val, x) => change(path, _ => val, x));\n\n/**\n * Omitting value referenced by path\n * (curried function)\n *\n * @function\n * @sig Path -> a -> a\n * @param {Path} path\n * @param {*} x\n * @return {*}\n */\nconst omit = curry((path, x) => {\n function _omit(path, x) {\n if (isInteger(path[0])) {\n x = (isArray(x) ? Array.from(x) : []);\n } else if (isString(path[0])) {\n x = (isObject(x) ? Object.assign({}, x) : {});\n } else {\n throw 'invalid path';\n }\n if (path.length > 1) {\n x[path[0]] = _omit(path.slice(1), x[path[0]]);\n } else if (isInteger(path[0])) {\n x.splice(path[0], 1);\n } else {\n delete x[path[0]];\n }\n return x;\n }\n path = flatten(path);\n if (path.length < 1)\n return undefined;\n return _omit(path, x);\n});\n\n/**\n * Change by moving value from source path to destination path\n * (curried function)\n *\n * @function\n * @sig Path -> Path -> a -> a\n * @param {Path} srcPath\n * @param {Path} dstPath\n * @param {*} x\n * @return {*}\n */\nconst move = curry((srcPath, dstPath, x) => pipe(\n set(dstPath, get(srcPath, x)),\n omit(srcPath)\n)(x));\n\n/**\n * Sort array\n * (curried function)\n *\n * Comparison function receives two arguments representing array elements and\n * should return:\n * - negative number in case first argument is more significant then second\n * - zero in case first argument is equaly significant as second\n * - positive number in case first argument is less significant then second\n *\n * @function\n * @sig ((a, a) -> Number) -> [a] -> [a]\n * @param {Function} fn\n * @param {Array} arr\n * @return {Array}\n */\nconst sort = curry((fn, arr) => Array.from(arr).sort(fn));\n\n/**\n * Sort array based on results of appling function to it's elements\n * (curried function)\n *\n * Resulting order is determined by comparring function application results\n * with greater then and lesser then operators.\n *\n * @function\n * @sig (a -> b) -> [a] -> [a]\n * @param {Function} fn\n * @param {Array} arr\n * @return {Array}\n */\nconst sortBy = curry((fn, arr) => sort((x, y) => {\n const xVal = fn(x);\n const yVal = fn(y);\n if (xVal < yVal)\n return -1;\n if (xVal > yVal)\n return 1;\n return 0;\n}, arr));\n\n/**\n * Create object containing only subset of selected properties\n * (curried function)\n *\n * @function\n * @sig [String] -> a -> a\n * @param {Array} arr\n * @param {Object} obj\n * @return {Object}\n */\nconst pick = curry((arr, obj) => {\n const ret = {};\n for (let i of arr)\n if (i in obj)\n ret[i] = obj[i];\n return ret;\n});\n\n/**\n * Change array or object by appling function to it's elements\n * (curried function)\n *\n * For each element, provided function is called with element value,\n * index/key and original container.\n *\n * @function\n * @sig ((a, Number, [a]) -> b) -> [a] -> [b]\n * @sig ((a, String, {String: a}) -> b) -> {String: a} -> {String: b}\n * @param {Function} fn\n * @param {Array|Object} x\n * @return {Array|Object}\n */\nconst map = curry((fn, x) => {\n if (isArray(x))\n return x.map(fn);\n const res = {};\n for (let k in x)\n res[k] = fn(x[k], k, x);\n return res;\n});\n\n/**\n * Change array to contain only elements for which function returns `true`\n * (curried function)\n *\n * @function\n * @sig (a -> Boolean) -> [a] -> [a]\n * @param {Function} fn\n * @param {Array} arr\n * @return {Array}\n */\nconst filter = curry((fn, arr) => arr.filter(fn));\n\n/**\n * Append value to end of array\n * (curried function)\n *\n * @function\n * @sig a -> [a] -> [a]\n * @param {*} val\n * @param {Array} arr\n * @return {Array}\n */\nconst append = curry((val, arr) => arr.concat([val]));\n\n/**\n * Reduce array values by appling function\n * (curried function)\n *\n * For each array element, provided function is called with accumulator,\n * current value, current index and source array.\n *\n * TODO: support objects\n *\n * @function\n * @sig ((b, a, Number, [a]) -> b) -> b -> [a] -> b\n * @param {Function} fn\n * @param {*} val initial accumulator value\n * @param {Array} arr\n * @return {*} reduced value\n */\nconst reduce = curry((fn, val, arr) => arr.reduce(fn, val));\n\n/**\n * Merge two objects\n * (curried function)\n *\n * If same property exist in both arguments, second argument's value is used\n * as resulting value\n *\n * @function\n * @sig a -> a -> a\n * @param {Object} x\n * @param {Object} y\n * @return {Object}\n */\nconst merge = curry((x, y) => Object.assign({}, x, y));\n\n/**\n * Merge multiple objects\n * (curried function)\n *\n * If same property exist in multiple arguments, value from the last argument\n * containing that property is used\n *\n * @function\n * @sig [a] -> a\n * @param {Object[]}\n * @return {Object}\n */\nconst mergeAll = reduce(merge, {});\n\n/**\n * Find element in array or object for which provided function returns `true`\n * (curried function)\n *\n * Until element is found, provided function is called for each element with\n * arguments: current element, current index/key and initial container.\n *\n * If searched element is not found, `undefined` is returned.\n *\n * @function\n * @sig ((a, Number, [a]) -> Boolean) -> [a] -> a\n * @sig ((a, String, {String: a}) -> Boolean) -> {String: a} -> a\n * @param {Function} fn\n * @param {Array|Object} x\n * @return {*}\n */\nconst find = curry((fn, x) => {\n if (isArray(x))\n return x.find(fn);\n for (let k in x)\n if (fn(x[k], k, x))\n return x[k];\n});\n\n/**\n * Find element's index/key in array or object for which provided function\n * returns `true`\n * (curried function)\n *\n * Until element is found, provided function is called for each element with\n * arguments: current element, current index/key and initial container.\n *\n * If searched element is not found, `undefined` is returned.\n *\n * @function\n * @sig ((a, Number, [a]) -> Boolean) -> [a] -> a\n * @sig ((a, String, {String: a}) -> Boolean) -> {String: a} -> a\n * @param {Function} fn\n * @param {Array|Object} x\n * @return {*}\n */\nconst findIndex = curry((fn, x) => {\n if (isArray(x))\n return x.findIndex(fn);\n for (let k in x)\n if (fn(x[k], k, x))\n return k;\n});\n\n/**\n * Concatenate two arrays\n * (curried function)\n *\n * @function\n * @sig [a] -> [a] -> [a]\n * @param {Array} x\n * @param {Array} y\n * @return {Array}\n */\nconst concat = curry((x, y) => x.concat(y));\n\n/**\n * Create union of two arrays using `equals` to check equality\n * (curried function)\n *\n * @function\n * @sig [a] -> [a] -> [a]\n * @param {Array} x\n * @param {Array} y\n * @return {Array}\n */\nconst union = curry((x, y) => {\n return reduce((acc, val) => {\n if (!find(equals(val), x))\n acc = append(val, acc);\n return acc;\n }, x, y);\n});\n\n/**\n * Check if array contains value\n * (curried function)\n *\n * TODO: add support for objects (should we check for keys or values?)\n *\n * @function\n * @sig a -> [a] -> Boolean\n * @param {*} val\n * @param {Array|Object} x\n * @return {Boolean}\n */\nconst contains = curry((val, arr) => arr.includes(val));\n\n/**\n * Insert value into array on specified index\n * (curried function)\n *\n * @function\n * @sig Number -> a -> [a] -> [a]\n * @param {Number} idx\n * @param {*} val\n * @param {Array} arr\n * @return {Array}\n */\nconst insert = curry((idx, val, arr) =>\n arr.slice(0, idx).concat([val], arr.slice(idx)));\n\n/**\n * Get array slice\n * (curried function)\n *\n * @function\n * @sig Number -> Number -> [a] -> [a]\n * @param {Number} begin\n * @param {Number} end\n * @param {Array} arr\n * @return {Array}\n */\nconst slice = curry((begin, end, arr) => arr.slice(begin, end));\n\n/**\n * Reverse array\n *\n * @function\n * @sig [a] -> [a]\n * @param {Array} arr\n * @return {Array}\n */\nfunction reverse(arr) {\n return Array.from(arr).reverse();\n}\n\n/**\n * Array length\n *\n * @function\n * @sig [a] -> Number\n * @param {Array} arr\n * @return {Number}\n */\nfunction length(arr) {\n return arr.length;\n}\n\n/**\n * Increment value\n * @param {Number} val\n * @return {Number}\n */\nfunction inc(val) {\n return val + 1;\n}\n\n/**\n * Decrement value\n * @param {Number} val\n * @return {Number}\n */\nfunction dec(val) {\n return val - 1;\n}\n\n/**\n * Logical not\n * @param {Any} val\n * @return {Boolean}\n */\nfunction not(val) {\n return !val;\n}\n\n/**\n * Create promise that resolves in `t` milliseconds\n *\n * TODO: move to other module\n *\n * @function\n * @sig Number -> Promise\n * @param {Number} t\n * @return {Promise}\n */\nfunction sleep(t) {\n return new Promise(resolve => {\n setTimeout(() => { resolve(); }, t);\n });\n}\n\n/**\n * Delay function call `fn(...args)` for `t` milliseconds\n *\n * TODO: move to other module\n *\n * @function\n * @sig (((a1, a2, ..., an) -> _), Number, a1, a2, ..., an) -> Promise\n * @param {Function} fn\n * @param {Number} [t=0]\n * @param {*} args\n * @return {Promise}\n */\nfunction delay(fn, t, ...args) {\n return new Promise(resolve => {\n setTimeout(() => { resolve(fn(...args)); }, t || 0);\n });\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9hcHAvLi9ub2RlX21vZHVsZXMvQGhhdC1jb3JlL3V0aWwvaW5kZXguanM/ZWIxMyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhLHVCQUF1QjtBQUNwQzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsWUFBWSxFQUFFO0FBQ2Q7QUFDTzs7QUFFUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixZQUFZO0FBQ1o7QUFDTzs7QUFFUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixZQUFZO0FBQ1o7QUFDTzs7QUFFUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsVUFBVTtBQUNWO0FBQ087O0FBRVA7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLFlBQVk7QUFDWjtBQUNPOztBQUVQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsSUFBSTtBQUNmLFVBQVU7QUFDVjtBQUNPOztBQUVQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLFlBQVk7QUFDWjtBQUNPOztBQUVQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNB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O0FBQ2xCLFlBQVk7QUFDWjtBQUNPO0FBQ1A7QUFDQSwwQkFBMEIsV0FBVyxFQUFFO0FBQ3ZDLEtBQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsU0FBUztBQUNwQixXQUFXLE9BQU87QUFDbEIsV0FBVyxFQUFFO0FBQ2IsWUFBWTtBQUNaO0FBQ087QUFDUDtBQUNBLDBCQUEwQixzQkFBc0IsRUFBRTtBQUNsRCxLQUFLO0FBQ0wiLCJmaWxlIjoiMTEuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFV0aWxpdHkgbGlicmFyeSBmb3IgbWFuaXB1bGF0aW9uIG9mIEpTT04gZGF0YS5cbiAqXG4gKiBNYWluIGNoYXJhY3RlcmlzdGljczpcbiAqICAgLSBpbnB1dC9vdXRwdXQgZGF0YSB0eXBlcyBhcmUgbGltaXRlZCB0byBKU09OIGRhdGEsIGZ1bmN0aW9ucyBhbmRcbiAqICAgICBgdW5kZWZpbmVkYCAoc3BhcnNlIGFycmF5cyBhbmQgY29tcGxleCBvYmplY3RzIHdpdGggcHJvdG90eXBlIGNoYWluIGFyZVxuICogICAgIG5vdCBzdXBwb3J0ZWQpXG4gKiAgIC0gZnVuY3Rpb25hbCBBUEkgd2l0aCBjdXJyaWVkIGZ1bmN0aW9ucyAoc2ltaWxhciB0byByYW1kYWpzKVxuICogICAtIGltcGxlbWVudGF0aW9uIGJhc2VkIG9uIG5hdGl2ZWx5IHN1cHBvcnRlZCBicm93c2VyIEpTIEFQSVxuICogICAtIHNjb3BlIGxpbWl0ZWQgdG8gbW9zdCB1c2VkIGZ1bmN0aW9ucyBpbiBoYXQgcHJvamVjdHNcbiAqICAgLSB1c2FnZSBvZiBgcGF0aHNgIGluc3RlYWQgb2YgYGxlbnNlc2BcbiAqXG4gKiBUT0RPOiBkZWZpbmUgY29udmV0aW9uIGZvciBuYW1pbmcgYXJndW1lbnRzIGJhc2VkIG9uIHRoZWlyIHR5cGUgYW5kXG4gKiAgICAgICBzZW1hbnRpY3NcbiAqXG4gKiBAbW9kdWxlIEBoYXQtY29yZS91dGlsXG4gKi9cblxuLyoqXG4gKiBQYXRoIGNhbiBiZSBhbiBvYmplY3QgcHJvcGVydHkgbmFtZSwgYXJyYXkgaW5kZXgsIG9yIGFycmF5IG9mIFBhdGhzXG4gKlxuICogVE9ETzogZXhwbGFpbiBwYXRocyBhbmQgcGF0aCBjb21wb3NpdGlvbnMgKGluY2x1ZGUgZXhhbXBsZXMpXG4gKlxuICogQHR5cGVkZWYgeyhTdHJpbmd8TnVtYmVyfFBhdGhbXSl9IG1vZHVsZTpAaGF0LWNvcmUvdXRpbC5QYXRoXG4gKi9cblxuLyoqXG4gKiBJZGVudGl0eSBmdW5jdGlvbiByZXR1cm5pbmcgc2FtZSB2YWx1ZSBwcm92aWRlZCBhcyBhcmd1bWVudC5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgYSAtPiBhXG4gKiBAcGFyYW0geyp9IHggaW5wdXQgdmFsdWVcbiAqIEByZXR1cm4geyp9IHNhbWUgdmFsdWUgYXMgaW5wdXRcbiAqL1xuZXhwb3J0IGNvbnN0IGlkZW50aXR5ID0geCA9PiB4O1xuXG4vKipcbiAqIENoZWNrIGlmIHZhbHVlIGlzIGBudWxsYCBvciBgdW5kZWZpbmVkYC5cbiAqXG4gKiBGb3Igc2FtZSBhcmd1bWVudCwgaWYgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGB0cnVlYCwgZnVuY3Rpb25zIGBpc0Jvb2xlYW5gLFxuICogYGlzSW50ZWdlcmAsIGBpc051bWJlcmAsIGBpc1N0cmluZ2AsIGBpc0FycmF5YCBhbmQgYGlzT2JqZWN0YCB3aWxsIHJldHVyblxuICogYGZhbHNlYC5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKiAtPiBCb29sZWFuXG4gKiBAcGFyYW0geyp9IHggaW5wdXQgdmFsdWVcbiAqIEByZXR1cm4ge0Jvb2xlYW59XG4gKi9cbmV4cG9ydCBjb25zdCBpc05pbCA9IHggPT4geCA9PSBudWxsO1xuXG4vKipcbiAqIENoZWNrIGlmIHZhbHVlIGlzIEJvb2xlYW4uXG4gKlxuICogRm9yIHNhbWUgYXJndW1lbnQsIGlmIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgdHJ1ZWAsIGZ1bmN0aW9ucyBgaXNOaWxgLFxuICogYGlzSW50ZWdlcmAsIGBpc051bWJlcmAsIGBpc1N0cmluZ2AsIGBpc0FycmF5YCBhbmQgYGlzT2JqZWN0YCB3aWxsIHJldHVyblxuICogYGZhbHNlYC5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKiAtPiBCb29sZWFuXG4gKiBAcGFyYW0geyp9IHggaW5wdXQgdmFsdWVcbiAqIEByZXR1cm4ge0Jvb2xlYW59XG4gKi9cbmV4cG9ydCBjb25zdCBpc0Jvb2xlYW4gPSB4ID0+IHR5cGVvZih4KSA9PSAnYm9vbGVhbic7XG5cbi8qKlxuICogQ2hlY2sgaWYgdmFsdWUgaXMgSW50ZWdlci5cbiAqXG4gKiBGb3Igc2FtZSBhcmd1bWVudCwgaWYgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGB0cnVlYCwgZnVuY3Rpb24gYGlzTnVtYmVyYCB3aWxsXG4gKiBhbHNvIHJldHVybiBgdHJ1ZWAuXG4gKlxuICogRm9yIHNhbWUgYXJndW1lbnQsIGlmIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgdHJ1ZWAsIGZ1bmN0aW9ucyBgaXNOaWxgLFxuICogYGlzQm9vbGVhbmAsIGBpc1N0cmluZ2AsIGBpc0FycmF5YCBhbmQgYGlzT2JqZWN0YCB3aWxsIHJldHVybiBgZmFsc2VgLlxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyAqIC0+IEJvb2xlYW5cbiAqIEBwYXJhbSB7Kn0geCBpbnB1dCB2YWx1ZVxuICogQHR5cGUge0Jvb2xlYW59XG4gKi9cbmV4cG9ydCBjb25zdCBpc0ludGVnZXIgPSBOdW1iZXIuaXNJbnRlZ2VyO1xuXG4vKipcbiAqIENoZWNrIGlmIHZhbHVlIGlzIE51bWJlci5cbiAqXG4gKiBGb3Igc2FtZSBhcmd1bWVudCwgaWYgdGhpcyBmdW5jdGlvbiByZXR1cm5zIGB0cnVlYCwgZnVuY3Rpb24gYGlzSW50ZWdlcmAgbWF5XG4gKiBhbHNvIHJldHVybiBgdHJ1ZWAgaWYgYXJndW1lbnQgaXMgaW50ZWdlciBudW1iZXIuXG4gKlxuICogRm9yIHNhbWUgYXJndW1lbnQsIGlmIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBgdHJ1ZWAsIGZ1bmN0aW9ucyBgaXNOaWxgLFxuICogYGlzQm9vbGVhbmAsIGBpc1N0cmluZ2AsIGBpc0FycmF5YCBhbmQgYGlzT2JqZWN0YCB3aWxsIHJldHVybiBgZmFsc2VgLlxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyAqIC0+IEJvb2xlYW5cbiAqIEBwYXJhbSB7Kn0geCBpbnB1dCB2YWx1ZVxuICogQHJldHVybiB7Qm9vbGVhbn1cbiAqL1xuZXhwb3J0IGNvbnN0IGlzTnVtYmVyID0geCA9PiB0eXBlb2YoeCkgPT0gJ251bWJlcic7XG5cbi8qKlxuICogQ2hlY2sgaWYgdmFsdWUgaXMgU3RyaW5nLlxuICpcbiAqIEZvciBzYW1lIGFyZ3VtZW50LCBpZiB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYHRydWVgLCBmdW5jdGlvbnMgYGlzTmlsYCxcbiAqIGBpc0Jvb2xlYW5gLCBgaXNJbnRlZ2VyYCwgYGlzTnVtYmVyYCwgYGlzQXJyYXlgLCBhbmQgYGlzT2JqZWN0YCB3aWxsIHJldHVyblxuICogYGZhbHNlYC5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKiAtPiBCb29sZWFuXG4gKiBAcGFyYW0ge0FueX0geCBpbnB1dCB2YWx1ZVxuICogQHR5cGUge0Jvb2xlYW59XG4gKi9cbmV4cG9ydCBjb25zdCBpc1N0cmluZyA9IHggPT4gdHlwZW9mKHgpID09ICdzdHJpbmcnO1xuXG4vKipcbiAqIENoZWNrIGlmIHZhbHVlIGlzIEFycmF5LlxuICpcbiAqIEZvciBzYW1lIGFyZ3VtZW50LCBpZiB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYHRydWVgLCBmdW5jdGlvbnMgYGlzTmlsYCxcbiAqIGBpc0Jvb2xlYW5gLCBgaXNJbnRlZ2VyYCwgYGlzTnVtYmVyYCwgYGlzU3RyaW5nYCwgYW5kIGBpc09iamVjdGAgd2lsbCByZXR1cm5cbiAqIGBmYWxzZWAuXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnICogLT4gQm9vbGVhblxuICogQHBhcmFtIHsqfSB4IGlucHV0IHZhbHVlXG4gKiBAcmV0dXJuIHtCb29sZWFufVxuICovXG5leHBvcnQgY29uc3QgaXNBcnJheSA9IEFycmF5LmlzQXJyYXk7XG5cbi8qKlxuICogQ2hlY2sgaWYgdmFsdWUgaXMgT2JqZWN0LlxuICpcbiAqIEZvciBzYW1lIGFyZ3VtZW50LCBpZiB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYHRydWVgLCBmdW5jdGlvbnMgYGlzTmlsYCxcbiAqIGBpc0Jvb2xlYW5gLCBgaXNJbnRlZ2VyYCwgYGlzTnVtYmVyYCwgYGlzU3RyaW5nYCwgYW5kIGBpc0FycmF5YCB3aWxsIHJldHVyblxuICogYGZhbHNlYC5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKiAtPiBCb29sZWFuXG4gKiBAcGFyYW0geyp9IHggaW5wdXQgdmFsdWVcbiAqIEByZXR1cm4ge0Jvb2xlYW59XG4gKi9cbmV4cG9ydCBjb25zdCBpc09iamVjdCA9IHggPT4gdHlwZW9mKHgpID09ICdvYmplY3QnICYmXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpc0FycmF5KHgpICYmXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpc05pbCh4KTtcblxuLyoqXG4gKiBTdHJpY3RseSBwYXJzZSBpbnRlZ2VyIGZyb20gc3RyaW5nXG4gKlxuICogSWYgcHJvdmlkZWQgc3RyaW5nIGRvZXNuJ3QgcmVwcmVzZW50IGludGVnZXIgdmFsdWUsIGBOYU5gIGlzIHJldHVybmVkLlxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBTdHJpbmcgLT4gTnVtYmVyXG4gKiBAcGFyYW0ge1N0cmluZ30gdmFsdWVcbiAqIEByZXR1cm4ge051bWJlcn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0cmljdFBhcnNlSW50KHZhbHVlKSB7XG4gICAgaWYgKC9eKC18XFwrKT8oWzAtOV0rKSQvLnRlc3QodmFsdWUpKVxuICAgICAgICByZXR1cm4gTnVtYmVyKHZhbHVlKTtcbiAgICByZXR1cm4gTmFOO1xufVxuXG4vKipcbiAqIFN0cmljdGx5IHBhcnNlIGZsb2F0aW5nIHBvaW50IG51bWJlciBmcm9tIHN0cmluZ1xuICpcbiAqIElmIHByb3ZpZGVkIHN0cmluZyBkb2Vzbid0IHJlcHJlc2VudCB2YWxpZCBudW1iZXIsIGBOYU5gIGlzIHJldHVybmVkLlxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBTdHJpbmcgLT4gTnVtYmVyXG4gKiBAcGFyYW0ge1N0cmluZ30gdmFsdWVcbiAqIEByZXR1cm4ge051bWJlcn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0cmljdFBhcnNlRmxvYXQodmFsdWUpIHtcbiAgICBpZiAoL14oLXxcXCspPyhbMC05XSsoXFwuWzAtOV0rKT8pJC8udGVzdCh2YWx1ZSkpXG4gICAgICAgIHJldHVybiBOdW1iZXIodmFsdWUpO1xuICAgIHJldHVybiBOYU47XG59XG5cbi8qKlxuICogQ3JlYXRlIG5ldyBkZWVwIGNvcHkgb2YgaW5wdXQgdmFsdWUuXG4gKlxuICogSW4gY2FzZSBvZiBPYmplY3RzIG9yIEFycmF5cywgbmV3IGluc3RhbmNlcyBhcmUgY3JlYXRlZCB3aXRoIGVsZW1lbnRzXG4gKiBvYnRhaW5lZCBieSByZWN1cnNpdmx5IGNhbGxpbmcgYGNsb25lYCBpbiBpbnB1dCBhcmd1bWVudCB2YWx1ZXMuXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnICogLT4gKlxuICogQHBhcmFtIHsqfSB4IHZhbHVlXG4gKiBAcmV0dXJuIHsqfSBjb3B5IG9mIHZhbHVlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjbG9uZSh4KSB7XG4gICAgaWYgKGlzQXJyYXkoeCkpXG4gICAgICAgIHJldHVybiBBcnJheS5mcm9tKHgsIGNsb25lKTtcbiAgICBpZiAoaXNPYmplY3QoeCkpIHtcbiAgICAgICAgbGV0IHJldCA9IHt9O1xuICAgICAgICBmb3IgKGxldCBpIGluIHgpXG4gICAgICAgICAgICByZXRbaV0gPSBjbG9uZSh4W2ldKTtcbiAgICAgICAgcmV0dXJuIHJldDtcbiAgICB9XG4gICAgcmV0dXJuIHg7XG59XG5cbi8qKlxuICogQ29tYmluZSB0d28gYXJyYXlzIGluIHNpbmdsZSBhcnJheSBvZiBwYWlyc1xuICpcbiAqIFRoZSByZXR1cm5lZCBhcnJheSBpcyB0cnVuY2F0ZWQgdG8gdGhlIGxlbmd0aCBvZiB0aGUgc2hvcnRlciBvZiB0aGUgdHdvXG4gKiBpbnB1dCBhcnJheXMuXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIFthXSAtPiBbYl0gLT4gW1thLGJdXVxuICogQHBhcmFtIHtBcnJheX0gYXJyMVxuICogQHBhcmFtIHtBcnJheX0gYXJyMlxuICogQHJldHVybiB7QXJyYXl9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB6aXAoYXJyMSwgYXJyMikge1xuICAgIHJldHVybiBBcnJheS5mcm9tKChmdW5jdGlvbiooKSB7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYXJyMS5sZW5ndGggfHwgaSA8IGFycjIubGVuZ3RoOyArK2kpXG4gICAgICAgICAgICB5aWVsZCBbYXJyMVtpXSwgYXJyMltpXV07XG4gICAgfSkoKSk7XG59XG5cbi8qKlxuICogQ29udmVydCBvYmplY3QgdG8gYXJyYXkgb2Yga2V5LCB2YWx1ZSBwYWlyc1xuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBPYmplY3QgLT4gW1tTdHJpbmcsKl1dXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqXG4gKiBAcmV0dXJuIHtBcnJheX1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvUGFpcnMob2JqKSB7XG4gICAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKG9iaik7XG59XG5cbi8qKlxuICogQ29udmVydCBhcnJheSBvZiBrZXksIHZhbHVlIHBhaXJzIHRvIG9iamVjdFxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBbW1N0cmluZywqXV0gLT4gT2JqZWN0XG4gKiBAcGFyYW0ge0FycmF5fSBhcnJcbiAqIEByZXR1cm4ge09iamVjdH1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZyb21QYWlycyhhcnIpIHtcbiAgICBsZXQgcmV0ID0ge307XG4gICAgZm9yIChsZXQgW2ssIHZdIG9mIGFycilcbiAgICAgICAgcmV0W2tdID0gdjtcbiAgICByZXR1cm4gcmV0O1xufVxuXG4vKipcbiAqIEZsYXR0ZW4gbmVzdGVkIGFycmF5cy5cbiAqXG4gKiBDcmVhdGUgYXJyYXkgd2l0aCBzYW1lIGVsZW1lbnRzIGFzIGluIGlucHV0IGFycmF5IHdoZXJlIGFsbCBlbGVtZW50cyB3aGljaFxuICogYXJlIGFsc28gYXJyYXlzIGFyZSByZXBsYWNlZCB3aXRoIGVsZW1lbnRzIG9mIHJlc3VsdGluZyByZWN1cnNpdmVcbiAqIGFwcGxpY2F0aW9uIG9mIGZsYXR0ZW4gZnVuY3Rpb24uXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIFthXSAtPiBbYl1cbiAqIEBwYXJhbSB7QXJyYXl9IGFyclxuICogQHJldHVybiB7QXJyYXl9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBmbGF0dGVuKGFycikge1xuICAgIHJldHVybiBBcnJheS5mcm9tKChmdW5jdGlvbiogZmxhdHRlbih4KSB7XG4gICAgICAgIGlmIChpc0FycmF5KHgpKSB7XG4gICAgICAgICAgICBmb3IgKGxldCBpIG9mIHgpXG4gICAgICAgICAgICAgICAgeWllbGQqIGZsYXR0ZW4oaSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB5aWVsZCB4O1xuICAgICAgICB9XG4gICAgfSkoYXJyKSk7XG59XG5cbi8qKlxuICogUGlwZSBmdW5jdGlvbiBjYWxsc1xuICpcbiAqIFBpcGUgcHJvdmlkZXMgZnVuY3Rpb25hbCBjb21wb3NpdGlvbiB3aXRoIHJldmVyc2VkIG9yZGVyLiBGaXJzdCBmdW5jdGlvblxuICogbWF5IGhhdmUgYW55IGFyaXR5IGFuZCBhbGwgb3RoZXIgZnVuY3Rpb25zIGFyZSBjYWxsZWQgd2l0aCBvbmx5IHNpbmdsZVxuICogYXJndW1lbnQgKHJlc3VsdCBmcm9tIHByZXZpb3VzIGZ1bmN0aW9uIGFwcGxpY2F0aW9uKS5cbiAqXG4gKiBJbiBjYXNlIHdoZW4gbm8gZnVuY3Rpb24gaXMgcHJvdmlkZWQsIHBpcGUgcmV0dXJucyBpZGVudGl0eSBmdW5jdGlvbi5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKCgoYTEsIGEyLCAuLi4sIGFuKSAtPiBiMSksIChiMSAtPiBiMiksIC4uLiwgKGJtMSAtPiBibSkpIC0+ICgoYTEsIGEyLCAuLi4sIGFuKSAtPiBibSlcbiAqIEBwYXJhbSB7Li4uRnVuY3Rpb259IGZucyBmdW5jdGlvbnNcbiAqIEByZXR1cm4ge0Z1bmN0aW9ufVxuICovXG5leHBvcnQgZnVuY3Rpb24gcGlwZSguLi5mbnMpIHtcbiAgICBpZiAoZm5zLmxlbmd0aCA8IDEpXG4gICAgICAgIHJldHVybiBpZGVudGl0eTtcbiAgICByZXR1cm4gZnVuY3Rpb24gKC4uLmFyZ3MpIHtcbiAgICAgICAgbGV0IHJldCA9IGZuc1swXS5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICAgICAgZm9yIChsZXQgZm4gb2YgZm5zLnNsaWNlKDEpKVxuICAgICAgICAgICAgcmV0ID0gZm4ocmV0KTtcbiAgICAgICAgcmV0dXJuIHJldDtcbiAgICB9O1xufVxuXG4vKipcbiAqIEFwcGx5IGxpc3Qgb2YgZnVuY3Rpb25zIHRvIHNhbWUgYXJndW1lbnRzIGFuZCByZXR1cm4gbGlzdCBvZiByZXN1bHRzXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnICgoYTEgLT4gLi4uIC0+IGFuIC0+IGIxKSwgLi4uLCAoYTEgLT4gLi4uIC0+IGFuIC0+IGJtKSkgLT4gKGExIC0+IC4uLiAtPiBhbiAtPiBbYjEsLi4uLGJtXSlcbiAqIEBwYXJhbSB7Li4uRnVuY3Rpb259IGZucyBmdW5jdGlvbnNcbiAqIEByZXR1cm4ge0Z1bmN0aW9ufVxuICovXG5leHBvcnQgZnVuY3Rpb24gZmxhcCguLi5mbnMpIHtcbiAgICByZXR1cm4gKC4uLmFyZ3MpID0+IGZucy5tYXAoZm4gPT4gZm4uYXBwbHkodGhpcywgYXJncykpO1xufVxuXG4vKipcbiAqIEN1cnJ5IGZ1bmN0aW9uIHdpdGggZml4ZWQgYXJndW1lbnRzIGxlbnRoXG4gKlxuICogRnVuY3Rpb24gYXJpdHkgaXMgZGV0ZXJtaW5lZCBiYXNlZCBvbiBmdW5jdGlvbidzIGxlbmd0aCBwcm9wZXJ0eS5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKCogLT4gYSkgLT4gKCogLT4gYSlcbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZuXG4gKiBAcmV0dXJuIHtGdW5jdGlvbn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGN1cnJ5KGZuKSB7XG4gICAgbGV0IHdyYXBwZXIgPSBmdW5jdGlvbihvbGRBcmdzKSB7XG4gICAgICAgIHJldHVybiBmdW5jdGlvbiguLi5hcmdzKSB7XG4gICAgICAgICAgICBhcmdzID0gb2xkQXJncy5jb25jYXQoYXJncyk7XG4gICAgICAgICAgICBpZiAoYXJncy5sZW5ndGggPj0gZm4ubGVuZ3RoKVxuICAgICAgICAgICAgICAgIHJldHVybiBmbiguLi5hcmdzKTtcbiAgICAgICAgICAgIHJldHVybiB3cmFwcGVyKGFyZ3MpO1xuICAgICAgICB9O1xuICAgIH07XG4gICAgcmV0dXJuIHdyYXBwZXIoW10pO1xufVxuXG4vKipcbiAqIERlZXAgb2JqZWN0IGVxdWFsaXR5XG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgYSAtPiBiIC0+IEJvb2xlYW5cbiAqIEBwYXJhbSB7Kn0geFxuICogQHBhcmFtIHsqfSB5XG4gKiBAcmV0dXJuIHtCb29sZWFufVxuICovXG5leHBvcnQgY29uc3QgZXF1YWxzID0gY3VycnkoKHgsIHkpID0+IHtcbiAgICBpZiAoeCA9PT0geSlcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgaWYgKHR5cGVvZih4KSAhPSAnb2JqZWN0JyB8fFxuICAgICAgICB0eXBlb2YoeSkgIT0gJ29iamVjdCcgfHxcbiAgICAgICAgeCA9PT0gbnVsbCB8fFxuICAgICAgICB5ID09PSBudWxsKVxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkoeCkgJiYgQXJyYXkuaXNBcnJheSh5KSkge1xuICAgICAgICBpZiAoeC5sZW5ndGggIT0geS5sZW5ndGgpXG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIGZvciAobGV0IFthLCBiXSBvZiB6aXAoeCwgeSkpIHtcbiAgICAgICAgICAgIGlmICghZXF1YWxzKGEsIGIpKVxuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGVsc2UgaWYgKCFBcnJheS5pc0FycmF5KHgpICYmICFBcnJheS5pc0FycmF5KHkpKSB7XG4gICAgICAgIGlmIChPYmplY3Qua2V5cyh4KS5sZW5ndGggIT0gT2JqZWN0LmtleXMoeSkubGVuZ3RoKVxuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICBmb3IgKGxldCBrZXkgaW4geCkge1xuICAgICAgICAgICAgaWYgKCEoa2V5IGluIHkpKVxuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICBmb3IgKGxldCBrZXkgaW4geCkge1xuICAgICAgICAgICAgaWYgKCFlcXVhbHMoeFtrZXldLCB5W2tleV0pKVxuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xufSk7XG5cblxuLyoqXG4gKiBDcmVhdGUgYXJyYXkgYnkgcmVwZWF0aW5nIHNhbWUgdmFsdWVcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBhIC0+IE51bWJlciAtPiBbYV1cbiAqIEBwYXJhbSB7Kn0geFxuICogQHBhcmFtIHtOdW1iZXJ9IG5cbiAqIEByZXR1cm4ge0FycmF5fVxuICovXG5leHBvcnQgY29uc3QgcmVwZWF0ID0gY3VycnkoKHgsIG4pID0+IEFycmF5LmZyb20oe2xlbmd0aDogbn0sIF8gPT4geCkpO1xuXG4vKipcbiAqIEdldCB2YWx1ZSByZWZlcmVuY2VkIGJ5IHBhdGhcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIElmIGlucHV0IHZhbHVlIGRvZXNuJ3QgY29udGFpbiBwcm92aWRlZCBwYXRoIHZhbHVlLCBgdW5kZWZpbmVkYCBpcyByZXR1cm5lZC5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgUGF0aCAtPiBhIC0+IGJcbiAqIEBwYXJhbSB7UGF0aH0gcGF0aFxuICogQHBhcmFtIHsqfSB4XG4gKiBAcmV0dXJuIHsqfVxuICovXG5leHBvcnQgY29uc3QgZ2V0ID0gY3VycnkoKHBhdGgsIHgpID0+IHtcbiAgICBsZXQgcmV0ID0geDtcbiAgICBmb3IgKGxldCBpIG9mIGZsYXR0ZW4ocGF0aCkpIHtcbiAgICAgICAgaWYgKHJldCA9PT0gbnVsbCB8fCB0eXBlb2YocmV0KSAhPSAnb2JqZWN0JylcbiAgICAgICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgICAgIHJldCA9IHJldFtpXTtcbiAgICB9XG4gICAgcmV0dXJuIHJldDtcbn0pO1xuXG4vKipcbiAqIENoYW5nZSB2YWx1ZSByZWZlcmVuY2VkIHdpdGggcGF0aCBieSBhcHBsaW5nIGZ1bmN0aW9uXG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgUGF0aCAtPiAoYSAtPiBiKSAtPiBjIC0+IGNcbiAqIEBwYXJhbSB7UGF0aH0gcGF0aFxuICogQHBhcmFtIHtGdW5jdGlvbn0gZm5cbiAqIEBwYXJhbSB7Kn0geFxuICogQHJldHVybiB7Kn1cbiAqL1xuZXhwb3J0IGNvbnN0IGNoYW5nZSA9IGN1cnJ5KChwYXRoLCBmbiwgeCkgPT4ge1xuICAgIHJldHVybiAoZnVuY3Rpb24gY2hhbmdlKHBhdGgsIHgpIHtcbiAgICAgICAgaWYgKHBhdGgubGVuZ3RoIDwgMSlcbiAgICAgICAgICAgIHJldHVybiBmbih4KTtcbiAgICAgICAgY29uc3QgW2ZpcnN0LCAuLi5yZXN0XSA9IHBhdGg7XG4gICAgICAgIGlmIChpc0ludGVnZXIoZmlyc3QpKSB7XG4gICAgICAgICAgICB4ID0gKGlzQXJyYXkoeCkgPyBBcnJheS5mcm9tKHgpIDogcmVwZWF0KHVuZGVmaW5lZCwgZmlyc3QpKTtcbiAgICAgICAgfSBlbHNlIGlmIChpc1N0cmluZyhmaXJzdCkpIHtcbiAgICAgICAgICAgIHggPSAoaXNPYmplY3QoeCkgPyBPYmplY3QuYXNzaWduKHt9LCB4KSA6IHt9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRocm93ICdpbnZhbGlkIHBhdGgnO1xuICAgICAgICB9XG4gICAgICAgIHhbZmlyc3RdID0gY2hhbmdlKHJlc3QsIHhbZmlyc3RdKTtcbiAgICAgICAgcmV0dXJuIHg7XG4gICAgfSkoZmxhdHRlbihwYXRoKSwgeCk7XG59KTtcblxuLyoqXG4gKiBSZXBsYWNlIHZhbHVlIHJlZmVyZW5jZWQgd2l0aCBwYXRoIHdpdGggYW5vdGhlciB2YWx1ZVxuICogKGN1cnJpZWQgZnVuY3Rpb24pXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIFBhdGggLT4gKGEgLT4gYikgLT4gYyAtPiBjXG4gKiBAcGFyYW0ge1BhdGh9IHBhdGhcbiAqIEBwYXJhbSB7Kn0gdmFsXG4gKiBAcGFyYW0geyp9IHhcbiAqIEByZXR1cm4geyp9XG4gKi9cbmV4cG9ydCBjb25zdCBzZXQgPSBjdXJyeSgocGF0aCwgdmFsLCB4KSA9PiBjaGFuZ2UocGF0aCwgXyA9PiB2YWwsIHgpKTtcblxuLyoqXG4gKiBPbWl0dGluZyB2YWx1ZSByZWZlcmVuY2VkIGJ5IHBhdGhcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBQYXRoIC0+IGEgLT4gYVxuICogQHBhcmFtIHtQYXRofSBwYXRoXG4gKiBAcGFyYW0geyp9IHhcbiAqIEByZXR1cm4geyp9XG4gKi9cbmV4cG9ydCBjb25zdCBvbWl0ID0gY3VycnkoKHBhdGgsIHgpID0+IHtcbiAgICBmdW5jdGlvbiBfb21pdChwYXRoLCB4KSB7XG4gICAgICAgIGlmIChpc0ludGVnZXIocGF0aFswXSkpIHtcbiAgICAgICAgICAgIHggPSAoaXNBcnJheSh4KSA/IEFycmF5LmZyb20oeCkgOiBbXSk7XG4gICAgICAgIH0gZWxzZSBpZiAoaXNTdHJpbmcocGF0aFswXSkpIHtcbiAgICAgICAgICAgIHggPSAoaXNPYmplY3QoeCkgPyBPYmplY3QuYXNzaWduKHt9LCB4KSA6IHt9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRocm93ICdpbnZhbGlkIHBhdGgnO1xuICAgICAgICB9XG4gICAgICAgIGlmIChwYXRoLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICAgIHhbcGF0aFswXV0gPSBfb21pdChwYXRoLnNsaWNlKDEpLCB4W3BhdGhbMF1dKTtcbiAgICAgICAgfSBlbHNlIGlmIChpc0ludGVnZXIocGF0aFswXSkpIHtcbiAgICAgICAgICAgIHguc3BsaWNlKHBhdGhbMF0sIDEpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZGVsZXRlIHhbcGF0aFswXV07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHg7XG4gICAgfVxuICAgIHBhdGggPSBmbGF0dGVuKHBhdGgpO1xuICAgIGlmIChwYXRoLmxlbmd0aCA8IDEpXG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIF9vbWl0KHBhdGgsIHgpO1xufSk7XG5cbi8qKlxuICogQ2hhbmdlIGJ5IG1vdmluZyB2YWx1ZSBmcm9tIHNvdXJjZSBwYXRoIHRvIGRlc3RpbmF0aW9uIHBhdGhcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBQYXRoIC0+IFBhdGggLT4gYSAtPiBhXG4gKiBAcGFyYW0ge1BhdGh9IHNyY1BhdGhcbiAqIEBwYXJhbSB7UGF0aH0gZHN0UGF0aFxuICogQHBhcmFtIHsqfSB4XG4gKiBAcmV0dXJuIHsqfVxuICovXG5leHBvcnQgY29uc3QgbW92ZSA9IGN1cnJ5KChzcmNQYXRoLCBkc3RQYXRoLCB4KSA9PiBwaXBlKFxuICAgIHNldChkc3RQYXRoLCBnZXQoc3JjUGF0aCwgeCkpLFxuICAgIG9taXQoc3JjUGF0aClcbikoeCkpO1xuXG4vKipcbiAqIFNvcnQgYXJyYXlcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIENvbXBhcmlzb24gZnVuY3Rpb24gcmVjZWl2ZXMgdHdvIGFyZ3VtZW50cyByZXByZXNlbnRpbmcgYXJyYXkgZWxlbWVudHMgYW5kXG4gKiBzaG91bGQgcmV0dXJuOlxuICogICAtIG5lZ2F0aXZlIG51bWJlciBpbiBjYXNlIGZpcnN0IGFyZ3VtZW50IGlzIG1vcmUgc2lnbmlmaWNhbnQgdGhlbiBzZWNvbmRcbiAqICAgLSB6ZXJvIGluIGNhc2UgZmlyc3QgYXJndW1lbnQgaXMgZXF1YWx5IHNpZ25pZmljYW50IGFzIHNlY29uZFxuICogICAtIHBvc2l0aXZlIG51bWJlciBpbiBjYXNlIGZpcnN0IGFyZ3VtZW50IGlzIGxlc3Mgc2lnbmlmaWNhbnQgdGhlbiBzZWNvbmRcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKChhLCBhKSAtPiBOdW1iZXIpIC0+IFthXSAtPiBbYV1cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZuXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJcbiAqIEByZXR1cm4ge0FycmF5fVxuICovXG5leHBvcnQgY29uc3Qgc29ydCA9IGN1cnJ5KChmbiwgYXJyKSA9PiBBcnJheS5mcm9tKGFycikuc29ydChmbikpO1xuXG4vKipcbiAqIFNvcnQgYXJyYXkgYmFzZWQgb24gcmVzdWx0cyBvZiBhcHBsaW5nIGZ1bmN0aW9uIHRvIGl0J3MgZWxlbWVudHNcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIFJlc3VsdGluZyBvcmRlciBpcyBkZXRlcm1pbmVkIGJ5IGNvbXBhcnJpbmcgZnVuY3Rpb24gYXBwbGljYXRpb24gcmVzdWx0c1xuICogd2l0aCBncmVhdGVyIHRoZW4gYW5kIGxlc3NlciB0aGVuIG9wZXJhdG9ycy5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKGEgLT4gYikgLT4gW2FdIC0+IFthXVxuICogQHBhcmFtIHtGdW5jdGlvbn0gZm5cbiAqIEBwYXJhbSB7QXJyYXl9IGFyclxuICogQHJldHVybiB7QXJyYXl9XG4gKi9cbmV4cG9ydCBjb25zdCBzb3J0QnkgPSBjdXJyeSgoZm4sIGFycikgPT4gc29ydCgoeCwgeSkgPT4ge1xuICAgIGNvbnN0IHhWYWwgPSBmbih4KTtcbiAgICBjb25zdCB5VmFsID0gZm4oeSk7XG4gICAgaWYgKHhWYWwgPCB5VmFsKVxuICAgICAgICByZXR1cm4gLTE7XG4gICAgaWYgKHhWYWwgPiB5VmFsKVxuICAgICAgICByZXR1cm4gMTtcbiAgICByZXR1cm4gMDtcbn0sIGFycikpO1xuXG4vKipcbiAqIENyZWF0ZSBvYmplY3QgY29udGFpbmluZyBvbmx5IHN1YnNldCBvZiBzZWxlY3RlZCBwcm9wZXJ0aWVzXG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgW1N0cmluZ10gLT4gYSAtPiBhXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmpcbiAqIEByZXR1cm4ge09iamVjdH1cbiAqL1xuZXhwb3J0IGNvbnN0IHBpY2sgPSBjdXJyeSgoYXJyLCBvYmopID0+IHtcbiAgICBjb25zdCByZXQgPSB7fTtcbiAgICBmb3IgKGxldCBpIG9mIGFycilcbiAgICAgICAgaWYgKGkgaW4gb2JqKVxuICAgICAgICAgICAgcmV0W2ldID0gb2JqW2ldO1xuICAgIHJldHVybiByZXQ7XG59KTtcblxuLyoqXG4gKiBDaGFuZ2UgYXJyYXkgb3Igb2JqZWN0IGJ5IGFwcGxpbmcgZnVuY3Rpb24gdG8gaXQncyBlbGVtZW50c1xuICogKGN1cnJpZWQgZnVuY3Rpb24pXG4gKlxuICogRm9yIGVhY2ggZWxlbWVudCwgcHJvdmlkZWQgZnVuY3Rpb24gaXMgY2FsbGVkIHdpdGggZWxlbWVudCB2YWx1ZSxcbiAqIGluZGV4L2tleSBhbmQgb3JpZ2luYWwgY29udGFpbmVyLlxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyAoKGEsIE51bWJlciwgW2FdKSAtPiBiKSAtPiBbYV0gLT4gW2JdXG4gKiBAc2lnICgoYSwgU3RyaW5nLCB7U3RyaW5nOiBhfSkgLT4gYikgLT4ge1N0cmluZzogYX0gLT4ge1N0cmluZzogYn1cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZuXG4gKiBAcGFyYW0ge0FycmF5fE9iamVjdH0geFxuICogQHJldHVybiB7QXJyYXl8T2JqZWN0fVxuICovXG5leHBvcnQgY29uc3QgbWFwID0gY3VycnkoKGZuLCB4KSA9PiB7XG4gICAgaWYgKGlzQXJyYXkoeCkpXG4gICAgICAgIHJldHVybiB4Lm1hcChmbik7XG4gICAgY29uc3QgcmVzID0ge307XG4gICAgZm9yIChsZXQgayBpbiB4KVxuICAgICAgICByZXNba10gPSBmbih4W2tdLCBrLCB4KTtcbiAgICByZXR1cm4gcmVzO1xufSk7XG5cbi8qKlxuICogQ2hhbmdlIGFycmF5IHRvIGNvbnRhaW4gb25seSBlbGVtZW50cyBmb3Igd2hpY2ggZnVuY3Rpb24gcmV0dXJucyBgdHJ1ZWBcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyAoYSAtPiBCb29sZWFuKSAtPiBbYV0gLT4gW2FdXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmblxuICogQHBhcmFtIHtBcnJheX0gYXJyXG4gKiBAcmV0dXJuIHtBcnJheX1cbiAqL1xuZXhwb3J0IGNvbnN0IGZpbHRlciA9IGN1cnJ5KChmbiwgYXJyKSA9PiBhcnIuZmlsdGVyKGZuKSk7XG5cbi8qKlxuICogQXBwZW5kIHZhbHVlIHRvIGVuZCBvZiBhcnJheVxuICogKGN1cnJpZWQgZnVuY3Rpb24pXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIGEgLT4gW2FdIC0+IFthXVxuICogQHBhcmFtIHsqfSB2YWxcbiAqIEBwYXJhbSB7QXJyYXl9IGFyclxuICogQHJldHVybiB7QXJyYXl9XG4gKi9cbmV4cG9ydCBjb25zdCBhcHBlbmQgPSBjdXJyeSgodmFsLCBhcnIpID0+IGFyci5jb25jYXQoW3ZhbF0pKTtcblxuLyoqXG4gKiBSZWR1Y2UgYXJyYXkgdmFsdWVzIGJ5IGFwcGxpbmcgZnVuY3Rpb25cbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIEZvciBlYWNoIGFycmF5IGVsZW1lbnQsIHByb3ZpZGVkIGZ1bmN0aW9uIGlzIGNhbGxlZCB3aXRoIGFjY3VtdWxhdG9yLFxuICogY3VycmVudCB2YWx1ZSwgY3VycmVudCBpbmRleCBhbmQgc291cmNlIGFycmF5LlxuICpcbiAqIFRPRE86IHN1cHBvcnQgb2JqZWN0c1xuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyAoKGIsIGEsIE51bWJlciwgW2FdKSAtPiBiKSAtPiBiIC0+IFthXSAtPiBiXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmblxuICogQHBhcmFtIHsqfSB2YWwgaW5pdGlhbCBhY2N1bXVsYXRvciB2YWx1ZVxuICogQHBhcmFtIHtBcnJheX0gYXJyXG4gKiBAcmV0dXJuIHsqfSByZWR1Y2VkIHZhbHVlXG4gKi9cbmV4cG9ydCBjb25zdCByZWR1Y2UgPSBjdXJyeSgoZm4sIHZhbCwgYXJyKSA9PiBhcnIucmVkdWNlKGZuLCB2YWwpKTtcblxuLyoqXG4gKiBNZXJnZSB0d28gb2JqZWN0c1xuICogKGN1cnJpZWQgZnVuY3Rpb24pXG4gKlxuICogSWYgc2FtZSBwcm9wZXJ0eSBleGlzdCBpbiBib3RoIGFyZ3VtZW50cywgc2Vjb25kIGFyZ3VtZW50J3MgdmFsdWUgaXMgdXNlZFxuICogYXMgcmVzdWx0aW5nIHZhbHVlXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIGEgLT4gYSAtPiBhXG4gKiBAcGFyYW0ge09iamVjdH0geFxuICogQHBhcmFtIHtPYmplY3R9IHlcbiAqIEByZXR1cm4ge09iamVjdH1cbiAqL1xuZXhwb3J0IGNvbnN0IG1lcmdlID0gY3VycnkoKHgsIHkpID0+IE9iamVjdC5hc3NpZ24oe30sIHgsIHkpKTtcblxuLyoqXG4gKiBNZXJnZSBtdWx0aXBsZSBvYmplY3RzXG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBJZiBzYW1lIHByb3BlcnR5IGV4aXN0IGluIG11bHRpcGxlIGFyZ3VtZW50cywgdmFsdWUgZnJvbSB0aGUgbGFzdCBhcmd1bWVudFxuICogY29udGFpbmluZyB0aGF0IHByb3BlcnR5IGlzIHVzZWRcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgW2FdIC0+IGFcbiAqIEBwYXJhbSB7T2JqZWN0W119XG4gKiBAcmV0dXJuIHtPYmplY3R9XG4gKi9cbmV4cG9ydCBjb25zdCBtZXJnZUFsbCA9IHJlZHVjZShtZXJnZSwge30pO1xuXG4vKipcbiAqIEZpbmQgZWxlbWVudCBpbiBhcnJheSBvciBvYmplY3QgZm9yIHdoaWNoIHByb3ZpZGVkIGZ1bmN0aW9uIHJldHVybnMgYHRydWVgXG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBVbnRpbCBlbGVtZW50IGlzIGZvdW5kLCBwcm92aWRlZCBmdW5jdGlvbiBpcyBjYWxsZWQgZm9yIGVhY2ggZWxlbWVudCB3aXRoXG4gKiBhcmd1bWVudHM6IGN1cnJlbnQgZWxlbWVudCwgY3VycmVudCBpbmRleC9rZXkgYW5kIGluaXRpYWwgY29udGFpbmVyLlxuICpcbiAqIElmIHNlYXJjaGVkIGVsZW1lbnQgaXMgbm90IGZvdW5kLCBgdW5kZWZpbmVkYCBpcyByZXR1cm5lZC5cbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgKChhLCBOdW1iZXIsIFthXSkgLT4gQm9vbGVhbikgLT4gW2FdIC0+IGFcbiAqIEBzaWcgKChhLCBTdHJpbmcsIHtTdHJpbmc6IGF9KSAtPiBCb29sZWFuKSAtPiB7U3RyaW5nOiBhfSAtPiBhXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmblxuICogQHBhcmFtIHtBcnJheXxPYmplY3R9IHhcbiAqIEByZXR1cm4geyp9XG4gKi9cbmV4cG9ydCBjb25zdCBmaW5kID0gY3VycnkoKGZuLCB4KSA9PiB7XG4gICAgaWYgKGlzQXJyYXkoeCkpXG4gICAgICAgIHJldHVybiB4LmZpbmQoZm4pO1xuICAgIGZvciAobGV0IGsgaW4geClcbiAgICAgICAgaWYgKGZuKHhba10sIGssIHgpKVxuICAgICAgICAgICAgcmV0dXJuIHhba107XG59KTtcblxuLyoqXG4gKiBGaW5kIGVsZW1lbnQncyBpbmRleC9rZXkgaW4gYXJyYXkgb3Igb2JqZWN0IGZvciB3aGljaCBwcm92aWRlZCBmdW5jdGlvblxuICogcmV0dXJucyBgdHJ1ZWBcbiAqIChjdXJyaWVkIGZ1bmN0aW9uKVxuICpcbiAqIFVudGlsIGVsZW1lbnQgaXMgZm91bmQsIHByb3ZpZGVkIGZ1bmN0aW9uIGlzIGNhbGxlZCBmb3IgZWFjaCBlbGVtZW50IHdpdGhcbiAqIGFyZ3VtZW50czogY3VycmVudCBlbGVtZW50LCBjdXJyZW50IGluZGV4L2tleSBhbmQgaW5pdGlhbCBjb250YWluZXIuXG4gKlxuICogSWYgc2VhcmNoZWQgZWxlbWVudCBpcyBub3QgZm91bmQsIGB1bmRlZmluZWRgIGlzIHJldHVybmVkLlxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyAoKGEsIE51bWJlciwgW2FdKSAtPiBCb29sZWFuKSAtPiBbYV0gLT4gYVxuICogQHNpZyAoKGEsIFN0cmluZywge1N0cmluZzogYX0pIC0+IEJvb2xlYW4pIC0+IHtTdHJpbmc6IGF9IC0+IGFcbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZuXG4gKiBAcGFyYW0ge0FycmF5fE9iamVjdH0geFxuICogQHJldHVybiB7Kn1cbiAqL1xuZXhwb3J0IGNvbnN0IGZpbmRJbmRleCA9IGN1cnJ5KChmbiwgeCkgPT4ge1xuICAgIGlmIChpc0FycmF5KHgpKVxuICAgICAgICByZXR1cm4geC5maW5kSW5kZXgoZm4pO1xuICAgIGZvciAobGV0IGsgaW4geClcbiAgICAgICAgaWYgKGZuKHhba10sIGssIHgpKVxuICAgICAgICAgICAgcmV0dXJuIGs7XG59KTtcblxuLyoqXG4gKiBDb25jYXRlbmF0ZSB0d28gYXJyYXlzXG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgW2FdIC0+IFthXSAtPiBbYV1cbiAqIEBwYXJhbSB7QXJyYXl9IHhcbiAqIEBwYXJhbSB7QXJyYXl9IHlcbiAqIEByZXR1cm4ge0FycmF5fVxuICovXG5leHBvcnQgY29uc3QgY29uY2F0ID0gY3VycnkoKHgsIHkpID0+IHguY29uY2F0KHkpKTtcblxuLyoqXG4gKiBDcmVhdGUgdW5pb24gb2YgdHdvIGFycmF5cyB1c2luZyBgZXF1YWxzYCB0byBjaGVjayBlcXVhbGl0eVxuICogKGN1cnJpZWQgZnVuY3Rpb24pXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIFthXSAtPiBbYV0gLT4gW2FdXG4gKiBAcGFyYW0ge0FycmF5fSB4XG4gKiBAcGFyYW0ge0FycmF5fSB5XG4gKiBAcmV0dXJuIHtBcnJheX1cbiAqL1xuZXhwb3J0IGNvbnN0IHVuaW9uID0gY3VycnkoKHgsIHkpID0+IHtcbiAgICByZXR1cm4gcmVkdWNlKChhY2MsIHZhbCkgPT4ge1xuICAgICAgICBpZiAoIWZpbmQoZXF1YWxzKHZhbCksIHgpKVxuICAgICAgICAgICAgYWNjID0gYXBwZW5kKHZhbCwgYWNjKTtcbiAgICAgICAgcmV0dXJuIGFjYztcbiAgICB9LCB4LCB5KTtcbn0pO1xuXG4vKipcbiAqIENoZWNrIGlmIGFycmF5IGNvbnRhaW5zIHZhbHVlXG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBUT0RPOiBhZGQgc3VwcG9ydCBmb3Igb2JqZWN0cyAoc2hvdWxkIHdlIGNoZWNrIGZvciBrZXlzIG9yIHZhbHVlcz8pXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIGEgLT4gW2FdIC0+IEJvb2xlYW5cbiAqIEBwYXJhbSB7Kn0gdmFsXG4gKiBAcGFyYW0ge0FycmF5fE9iamVjdH0geFxuICogQHJldHVybiB7Qm9vbGVhbn1cbiAqL1xuZXhwb3J0IGNvbnN0IGNvbnRhaW5zID0gY3VycnkoKHZhbCwgYXJyKSA9PiBhcnIuaW5jbHVkZXModmFsKSk7XG5cbi8qKlxuICogSW5zZXJ0IHZhbHVlIGludG8gYXJyYXkgb24gc3BlY2lmaWVkIGluZGV4XG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgTnVtYmVyIC0+IGEgLT4gW2FdIC0+IFthXVxuICogQHBhcmFtIHtOdW1iZXJ9IGlkeFxuICogQHBhcmFtIHsqfSB2YWxcbiAqIEBwYXJhbSB7QXJyYXl9IGFyclxuICogQHJldHVybiB7QXJyYXl9XG4gKi9cbmV4cG9ydCBjb25zdCBpbnNlcnQgPSBjdXJyeSgoaWR4LCB2YWwsIGFycikgPT5cbiAgICBhcnIuc2xpY2UoMCwgaWR4KS5jb25jYXQoW3ZhbF0sIGFyci5zbGljZShpZHgpKSk7XG5cbi8qKlxuICogR2V0IGFycmF5IHNsaWNlXG4gKiAoY3VycmllZCBmdW5jdGlvbilcbiAqXG4gKiBAZnVuY3Rpb25cbiAqIEBzaWcgTnVtYmVyIC0+IE51bWJlciAtPiBbYV0gLT4gW2FdXG4gKiBAcGFyYW0ge051bWJlcn0gYmVnaW5cbiAqIEBwYXJhbSB7TnVtYmVyfSBlbmRcbiAqIEBwYXJhbSB7QXJyYXl9IGFyclxuICogQHJldHVybiB7QXJyYXl9XG4gKi9cbmV4cG9ydCBjb25zdCBzbGljZSA9IGN1cnJ5KChiZWdpbiwgZW5kLCBhcnIpID0+IGFyci5zbGljZShiZWdpbiwgZW5kKSk7XG5cbi8qKlxuICogUmV2ZXJzZSBhcnJheVxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBbYV0gLT4gW2FdXG4gKiBAcGFyYW0gIHtBcnJheX0gYXJyXG4gKiBAcmV0dXJuIHtBcnJheX1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJldmVyc2UoYXJyKSB7XG4gICAgcmV0dXJuIEFycmF5LmZyb20oYXJyKS5yZXZlcnNlKCk7XG59XG5cbi8qKlxuICogQXJyYXkgbGVuZ3RoXG4gKlxuICogQGZ1bmN0aW9uXG4gKiBAc2lnIFthXSAtPiBOdW1iZXJcbiAqIEBwYXJhbSAge0FycmF5fSBhcnJcbiAqIEByZXR1cm4ge051bWJlcn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxlbmd0aChhcnIpIHtcbiAgICByZXR1cm4gYXJyLmxlbmd0aDtcbn1cblxuLyoqXG4gKiBJbmNyZW1lbnQgdmFsdWVcbiAqIEBwYXJhbSAge051bWJlcn0gdmFsXG4gKiBAcmV0dXJuIHtOdW1iZXJ9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbmModmFsKSB7XG4gICAgcmV0dXJuIHZhbCArIDE7XG59XG5cbi8qKlxuICogRGVjcmVtZW50IHZhbHVlXG4gKiBAcGFyYW0gIHtOdW1iZXJ9IHZhbFxuICogQHJldHVybiB7TnVtYmVyfVxuICovXG5leHBvcnQgZnVuY3Rpb24gZGVjKHZhbCkge1xuICAgIHJldHVybiB2YWwgLSAxO1xufVxuXG4vKipcbiAqIExvZ2ljYWwgbm90XG4gKiBAcGFyYW0gIHtBbnl9IHZhbFxuICogQHJldHVybiB7Qm9vbGVhbn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5vdCh2YWwpIHtcbiAgICByZXR1cm4gIXZhbDtcbn1cblxuLyoqXG4gKiBDcmVhdGUgcHJvbWlzZSB0aGF0IHJlc29sdmVzIGluIGB0YCBtaWxsaXNlY29uZHNcbiAqXG4gKiBUT0RPOiBtb3ZlIHRvIG90aGVyIG1vZHVsZVxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyBOdW1iZXIgLT4gUHJvbWlzZVxuICogQHBhcmFtIHtOdW1iZXJ9IHRcbiAqIEByZXR1cm4ge1Byb21pc2V9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzbGVlcCh0KSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKHJlc29sdmUgPT4ge1xuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHsgcmVzb2x2ZSgpOyB9LCB0KTtcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBEZWxheSBmdW5jdGlvbiBjYWxsIGBmbiguLi5hcmdzKWAgZm9yIGB0YCBtaWxsaXNlY29uZHNcbiAqXG4gKiBUT0RPOiBtb3ZlIHRvIG90aGVyIG1vZHVsZVxuICpcbiAqIEBmdW5jdGlvblxuICogQHNpZyAoKChhMSwgYTIsIC4uLiwgYW4pIC0+IF8pLCBOdW1iZXIsIGExLCBhMiwgLi4uLCBhbikgLT4gUHJvbWlzZVxuICogQHBhcmFtIHtGdW5jdGlvbn0gZm5cbiAqIEBwYXJhbSB7TnVtYmVyfSBbdD0wXVxuICogQHBhcmFtIHsqfSBhcmdzXG4gKiBAcmV0dXJuIHtQcm9taXNlfVxuICovXG5leHBvcnQgZnVuY3Rpb24gZGVsYXkoZm4sIHQsIC4uLmFyZ3MpIHtcbiAgICByZXR1cm4gbmV3IFByb21pc2UocmVzb2x2ZSA9PiB7XG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4geyByZXNvbHZlKGZuKC4uLmFyZ3MpKTsgfSwgdCB8fCAwKTtcbiAgICB9KTtcbn1cbiJdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///11\n"); + +/***/ }) +/******/ ]); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module can't be inlined because the eval-source-map devtool is used. +/******/ var __webpack_exports__ = __webpack_require__(0); +/******/ var __webpack_export_target__ = exports; +/******/ for(var i in __webpack_exports__) __webpack_export_target__[i] = __webpack_exports__[i]; +/******/ if(__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, "__esModule", { value: true }); +/******/ +/******/ })() +; diff --git a/examples/0002/view/grid/index.js.map b/examples/0002/view/grid/index.js.map new file mode 100644 index 0000000..59278dc --- /dev/null +++ b/examples/0002/view/grid/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack://app/./node_modules/@hat-core/renderer/index.js","webpack://app/./node_modules/snabbdom/build/package/init.js","webpack://app/./node_modules/snabbdom/build/package/vnode.js","webpack://app/./node_modules/snabbdom/build/package/is.js","webpack://app/./node_modules/snabbdom/build/package/htmldomapi.js","webpack://app/./node_modules/snabbdom/build/package/h.js","webpack://app/./node_modules/snabbdom/build/package/modules/class.js","webpack://app/./node_modules/snabbdom/build/package/modules/dataset.js","webpack://app/./node_modules/snabbdom/build/package/modules/eventlisteners.js","webpack://app/./node_modules/snabbdom/build/package/modules/style.js","webpack://app/./node_modules/@hat-core/util/index.js","webpack://app/webpack/bootstrap","webpack://app/webpack/runtime/define property getters","webpack://app/webpack/runtime/hasOwnProperty shorthand","webpack://app/webpack/runtime/make namespace object","webpack://app/./modules/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AACA;;AAEmD;AACT;AAC0B;AACM;AACY;AAClB;;AAEhC;;;AAGpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ,CAAC;;;AAGD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ,CAAC;;;AAGD,cAAc,mDAAY;AAC1B;AACA,IAAI,+DAAa;AACjB;AACA,IAAI,mEAAe;AACnB,IAAI,iFAAa;AACjB;;;AAGA;AACA;AACA;AACA,QAAQ,oDAAU;AAClB;AACA,SAAS,mDAAS;AAClB;AACA;AACA;AACA;AACA;AACA,uCAAuC,oDAAU;AACjD,qBAAqB,gDAAM;AAC3B,QAAQ,+CAAK;AACb,QAAQ,mDAAS;AACjB;AACA;AACA;AACA,QAAQ,6CAAS;AACjB,QAAQ,6CAAS;AACjB;AACA;;AAEA;AACA;AACA;AACO;;AAEP;AACA;AACA,eAAe,YAAY;AAC3B,eAAe,IAAI;AACnB,eAAe,SAAS;AACxB,eAAe,OAAO;AACtB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,eAAe,YAAY;AAC3B,eAAe,IAAI;AACnB,eAAe,SAAS;AACxB,eAAe,OAAO;AACtB,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,oDAAU;AACjC;AACA;AACA;AACA,uCAAuC,WAAW,EAAE;AACpD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,oBAAoB;AAC1E;;AAEA;AACA;AACA,eAAe,QAAQ;AACvB,gBAAgB;AAChB;AACA;AACA,eAAe,+CAAK;AACpB;;AAEA;AACA;AACA,eAAe,KAAK;AACpB,eAAe,IAAI;AACnB,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,eAAe,KAAK;AACpB,eAAe,SAAS;AACxB,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;;AAEA;AACA;AACA;AACA;AACA,yBAAyB,+CAAK;AAC9B;AACA,0BAA0B,kDAAQ;AAClC,+BAA+B,kDAAQ;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,2CAA2C,oBAAoB;AAC/D;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;AClQI;AACL;AACe;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,gDAAK,OAAO;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,aAAa;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,sDAAU;AAC1D,eAAe,kBAAkB;AACjC;AACA,mBAAmB,oBAAoB;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,gDAAK,4CAA4C;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,uBAAuB;AAC9C;AACA,gBAAgB,yCAAQ;AACxB,2BAA2B,qBAAqB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,6CAAY;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,oBAAoB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,wBAAwB;AACnD;AACA;AACA,+BAA+B,2BAA2B;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,oBAAoB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,uBAAuB;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,uBAAuB;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,oBAAoB;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,+BAA+B;AAClD;AACA;AACA,mBAAmB,qBAAqB;AACxC;AACA;AACA;AACA;AACA,gC;;;;;;;;;;AChUO;AACP;AACA,YAAY;AACZ;AACA,iC;;;;;;;;;;;ACJO;AACA;AACP;AACA;AACA,8B;;;;;;;;;;ACJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sC;;;;;;;;;;;;AC9DmC;AACL;AAC9B;AACA;AACA;AACA,uBAAuB,qBAAqB;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yCAAQ;AACpB;AACA;AACA,iBAAiB,6CAAY;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,yCAAQ;AACpB;AACA;AACA,iBAAiB,6CAAY;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,qBAAqB;AACxC,gBAAgB,6CAAY;AAC5B,8BAA8B,gDAAK;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,gDAAK;AAChB;AACA;AACA,6B;;;;;;;;;;AC3DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,qBAAqB;AAC5B,iC;;;;;;;;;;AC3BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,uBAAuB;AAC9B,mC;;;;;;;;;;ACrCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,oBAAoB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,0C;;;;;;;;;;AClFA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU,kBAAkB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA,iC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,aAAa,uBAAuB;AACpC;;AAEA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,YAAY,EAAE;AACd;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,UAAU;AACV;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,IAAI;AACf,UAAU;AACV;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,OAAO;AAClB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,OAAO;AAClB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,YAAY,EAAE;AACd;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;AACP;AACA,uBAAuB,oCAAoC;AAC3D;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA,WAAW,OAAO;AAClB,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY;AACvB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,WAAW,YAAY;AACvB,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,WAAW,EAAE;AACb,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;;AAGD;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,WAAW,OAAO;AAClB,YAAY;AACZ;AACO,2CAA2C,UAAU;;AAE5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,KAAK;AAChB,WAAW,EAAE;AACb,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,KAAK;AAChB,WAAW,SAAS;AACpB,WAAW,EAAE;AACb,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,+CAA+C,SAAS;AACxD,SAAS;AACT;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,KAAK;AAChB,WAAW,EAAE;AACb,WAAW,EAAE;AACb,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,KAAK;AAChB,WAAW,EAAE;AACb,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA,SAAS;AACT,+CAA+C,SAAS;AACxD,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,KAAK;AAChB,WAAW,KAAK;AAChB,WAAW,EAAE;AACb,YAAY;AACZ;AACO;AACP;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB,WAAW,OAAO;AAClB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,UAAU,YAAY,UAAU,KAAK;AAC3D,WAAW,SAAS;AACpB,WAAW,aAAa;AACxB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB,WAAW,EAAE;AACb,WAAW,MAAM;AACjB,YAAY,EAAE;AACd;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,OAAO;AAClB,WAAW,OAAO;AAClB,YAAY;AACZ;AACO,8CAA8C;;AAErD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX,YAAY;AACZ;AACO,iCAAiC;;AAExC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,UAAU,kBAAkB,UAAU;AAC5D,WAAW,SAAS;AACpB,WAAW,aAAa;AACxB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,UAAU,kBAAkB,UAAU;AAC5D,WAAW,SAAS;AACpB,WAAW,aAAa;AACxB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;AACP;AACA;AACA;AACA;AACA,KAAK;AACL,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,EAAE;AACb,WAAW,aAAa;AACxB,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,OAAO;AAClB,WAAW,EAAE;AACb,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;AACP;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,OAAO;AAClB,WAAW,OAAO;AAClB,WAAW,MAAM;AACjB,YAAY;AACZ;AACO;;AAEP;AACA;AACA;AACA;AACA;AACA,YAAY,MAAM;AAClB,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,YAAY,MAAM;AAClB,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA,YAAY,OAAO;AACnB,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA,YAAY,OAAO;AACnB,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA,YAAY,IAAI;AAChB,YAAY;AACZ;AACO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,OAAO;AAClB,YAAY;AACZ;AACO;AACP;AACA,0BAA0B,WAAW,EAAE;AACvC,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,SAAS;AACpB,WAAW,OAAO;AAClB,WAAW,EAAE;AACb,YAAY;AACZ;AACO;AACP;AACA,0BAA0B,sBAAsB,EAAE;AAClD,KAAK;AACL;;;;;;UC/0BA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCrBA;WACA;WACA;WACA;WACA,wCAAwC,yCAAyC;WACjF;WACA;WACA,E;;;;;WCPA,wF;;;;;WCAA;WACA;WACA;WACA,sDAAsD,kBAAkB;WACxE;WACA,+CAA+C,cAAc;WAC7D,E;;;;;;;;;;;;;;ACNmC;;;AAG5B;AACP,UAAU,2DAAK,WAAW;AAC1B;;;AAGO;AACP;AACA;;;AAGO","file":"index.js","sourcesContent":["/** @module @hat-core/renderer\n */\n\nimport {init as snabbdomInit} from 'snabbdom/init';\nimport {h as snabbdomH} from 'snabbdom/h';\nimport {classModule as snabbdomClass} from 'snabbdom/modules/class';\nimport {datasetModule as snabbdomDataset} from 'snabbdom/modules/dataset';\nimport {eventListenersModule as snabbdomEvent} from 'snabbdom/modules/eventlisteners';\nimport {styleModule as snabbdomStyle} from 'snabbdom/modules/style';\n\nimport * as u from '@hat-core/util';\n\n\n// patched version of snabbdom's es/modules/attributes.js\nconst snabbdomAttributes = (() => {\n function updateAttrs(oldVnode, vnode) {\n var key, elm = vnode.elm, oldAttrs = oldVnode.data.attrs, attrs = vnode.data.attrs;\n if (!oldAttrs && !attrs)\n return;\n if (oldAttrs === attrs)\n return;\n oldAttrs = oldAttrs || {};\n attrs = attrs || {};\n for (key in attrs) {\n var cur = attrs[key];\n var old = oldAttrs[key];\n if (old !== cur) {\n if (cur === true) {\n elm.setAttribute(key, \"\");\n }\n else if (cur === false) {\n elm.removeAttribute(key);\n }\n else {\n elm.setAttribute(key, cur);\n }\n }\n }\n for (key in oldAttrs) {\n if (!(key in attrs)) {\n elm.removeAttribute(key);\n }\n }\n }\n return { create: updateAttrs, update: updateAttrs };\n})();\n\n\n// patched version of snabbdom's es/modules/props.js\nconst snabbdomProps = (() => {\n function updateProps(oldVnode, vnode) {\n var key, cur, old, elm = vnode.elm, oldProps = oldVnode.data.props, props = vnode.data.props;\n if (!oldProps && !props)\n return;\n if (oldProps === props)\n return;\n oldProps = oldProps || {};\n props = props || {};\n for (key in oldProps) {\n if (!props[key]) {\n if (key === 'style') {\n elm[key] = '';\n } else {\n delete elm[key];\n }\n }\n }\n for (key in props) {\n cur = props[key];\n old = oldProps[key];\n if (old !== cur && (key !== 'value' || elm[key] !== cur)) {\n elm[key] = cur;\n }\n }\n }\n return { create: updateProps, update: updateProps };\n})();\n\n\nconst patch = snabbdomInit([\n snabbdomAttributes,\n snabbdomClass,\n snabbdomProps,\n snabbdomDataset,\n snabbdomEvent\n]);\n\n\nfunction vhFromArray(node) {\n if (!node)\n return [];\n if (u.isString(node))\n return node;\n if (!u.isArray(node))\n throw 'Invalid node structure';\n if (node.length < 1)\n return [];\n if (typeof node[0] != 'string')\n return node.map(vhFromArray);\n const hasData = node.length > 1 && u.isObject(node[1]);\n const children = u.pipe(\n u.map(vhFromArray),\n u.flatten,\n Array.from\n )(node.slice(hasData ? 2 : 1));\n const result = hasData ?\n snabbdomH(node[0], node[1], children) :\n snabbdomH(node[0], children);\n return result;\n}\n\n/**\n * Virtual DOM renderer\n */\nexport class Renderer extends EventTarget {\n\n /**\n * Calls `init` method\n * @param {HTMLElement} [el=document.body]\n * @param {Any} [initState=null]\n * @param {Function} [vtCb=null]\n * @param {Number} [maxFps=30]\n */\n constructor(el, initState, vtCb, maxFps) {\n super();\n this.init(el, initState, vtCb, maxFps);\n }\n\n /**\n * Initialize renderer\n * @param {HTMLElement} [el=document.body]\n * @param {Any} [initState=null]\n * @param {Function} [vtCb=null]\n * @param {Number} [maxFps=30]\n * @return {Promise}\n */\n init(el, initState, vtCb, maxFps) {\n this._state = null;\n this._changes = [];\n this._promise = null;\n this._timeout = null;\n this._lastRender = null;\n this._vtCb = vtCb;\n this._maxFps = u.isNumber(maxFps) ? maxFps : 30;\n this._vNode = el || document.querySelector('body');\n if (initState)\n return this.change(_ => initState);\n return new Promise(resolve => { resolve(); });\n }\n\n /**\n * Render\n */\n render() {\n if (!this._vtCb)\n return;\n this._lastRender = performance.now();\n const vNode = vhFromArray(this._vtCb(this));\n patch(this._vNode, vNode);\n this._vNode = vNode;\n this.dispatchEvent(new CustomEvent('render', {detail: this._state}));\n }\n\n /**\n * Get current state value referenced by `paths`\n * @param {...Path} paths\n * @return {Any}\n */\n get(...paths) {\n return u.get(paths, this._state);\n }\n\n /**\n * Change current state value referenced by `path`\n * @param {Path} path\n * @param {Any} value\n * @return {Promise}\n */\n set(path, value) {\n if (arguments.length < 2) {\n value = path;\n path = [];\n }\n return this.change(path, _ => value);\n }\n\n /**\n * Change current state value referenced by `path`\n * @param {Path} path\n * @param {Function} cb\n * @return {Promise}\n */\n change(path, cb) {\n if (arguments.length < 2) {\n cb = path;\n path = [];\n }\n this._changes.push([path, cb]);\n if (this._promise)\n return this._promise;\n this._promise = new Promise((resolve, reject) => {\n setTimeout(() => {\n try {\n this._change();\n } catch(e) {\n this._promise = null;\n reject(e);\n throw e;\n }\n this._promise = null;\n resolve();\n }, 0);\n });\n return this._promise;\n }\n\n _change() {\n let change = false;\n while (this._changes.length > 0) {\n const [path, cb] = this._changes.shift();\n const view = u.get(path);\n const oldState = this._state;\n this._state = u.change(path, cb, this._state);\n if (this._state && u.equals(view(oldState),\n view(this._state)))\n continue;\n change = true;\n if (!this._vtCb || this._timeout)\n continue;\n const delay = (!this._lastRender || !this._maxFps ?\n 0 :\n (1000 / this._maxFps) -\n (performance.now() - this._lastRender));\n this._timeout = setTimeout(() => {\n this._timeout = null;\n this.render();\n }, (delay > 0 ? delay : 0));\n }\n if (change)\n this.dispatchEvent(\n new CustomEvent('change', {detail: this._state}));\n }\n}\n// Renderer.prototype.set = u.curry(Renderer.prototype.set);\n// Renderer.prototype.change = u.curry(Renderer.prototype.change);\n\n\n/**\n * Default renderer\n * @static\n * @type {Renderer}\n */\nconst defaultRenderer = (() => {\n const r = (window && window.__hat_default_renderer) || new Renderer();\n if (window)\n window.__hat_default_renderer = r;\n return r;\n})();\nexport default defaultRenderer;\n","import { vnode } from \"./vnode.js\";\nimport * as is from \"./is.js\";\nimport { htmlDomApi } from \"./htmldomapi.js\";\nfunction isUndef(s) {\n return s === undefined;\n}\nfunction isDef(s) {\n return s !== undefined;\n}\nconst emptyNode = vnode('', {}, [], undefined, undefined);\nfunction sameVnode(vnode1, vnode2) {\n return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;\n}\nfunction isVnode(vnode) {\n return vnode.sel !== undefined;\n}\nfunction createKeyToOldIdx(children, beginIdx, endIdx) {\n var _a;\n const map = {};\n for (let i = beginIdx; i <= endIdx; ++i) {\n const key = (_a = children[i]) === null || _a === void 0 ? void 0 : _a.key;\n if (key !== undefined) {\n map[key] = i;\n }\n }\n return map;\n}\nconst hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post'];\nexport function init(modules, domApi) {\n let i;\n let j;\n const cbs = {\n create: [],\n update: [],\n remove: [],\n destroy: [],\n pre: [],\n post: []\n };\n const api = domApi !== undefined ? domApi : htmlDomApi;\n for (i = 0; i < hooks.length; ++i) {\n cbs[hooks[i]] = [];\n for (j = 0; j < modules.length; ++j) {\n const hook = modules[j][hooks[i]];\n if (hook !== undefined) {\n cbs[hooks[i]].push(hook);\n }\n }\n }\n function emptyNodeAt(elm) {\n const id = elm.id ? '#' + elm.id : '';\n const c = elm.className ? '.' + elm.className.split(' ').join('.') : '';\n return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);\n }\n function createRmCb(childElm, listeners) {\n return function rmCb() {\n if (--listeners === 0) {\n const parent = api.parentNode(childElm);\n api.removeChild(parent, childElm);\n }\n };\n }\n function createElm(vnode, insertedVnodeQueue) {\n var _a, _b;\n let i;\n let data = vnode.data;\n if (data !== undefined) {\n const init = (_a = data.hook) === null || _a === void 0 ? void 0 : _a.init;\n if (isDef(init)) {\n init(vnode);\n data = vnode.data;\n }\n }\n const children = vnode.children;\n const sel = vnode.sel;\n if (sel === '!') {\n if (isUndef(vnode.text)) {\n vnode.text = '';\n }\n vnode.elm = api.createComment(vnode.text);\n }\n else if (sel !== undefined) {\n // Parse selector\n const hashIdx = sel.indexOf('#');\n const dotIdx = sel.indexOf('.', hashIdx);\n const hash = hashIdx > 0 ? hashIdx : sel.length;\n const dot = dotIdx > 0 ? dotIdx : sel.length;\n const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;\n const elm = vnode.elm = isDef(data) && isDef(i = data.ns)\n ? api.createElementNS(i, tag)\n : api.createElement(tag);\n if (hash < dot)\n elm.setAttribute('id', sel.slice(hash + 1, dot));\n if (dotIdx > 0)\n elm.setAttribute('class', sel.slice(dot + 1).replace(/\\./g, ' '));\n for (i = 0; i < cbs.create.length; ++i)\n cbs.create[i](emptyNode, vnode);\n if (is.array(children)) {\n for (i = 0; i < children.length; ++i) {\n const ch = children[i];\n if (ch != null) {\n api.appendChild(elm, createElm(ch, insertedVnodeQueue));\n }\n }\n }\n else if (is.primitive(vnode.text)) {\n api.appendChild(elm, api.createTextNode(vnode.text));\n }\n const hook = vnode.data.hook;\n if (isDef(hook)) {\n (_b = hook.create) === null || _b === void 0 ? void 0 : _b.call(hook, emptyNode, vnode);\n if (hook.insert) {\n insertedVnodeQueue.push(vnode);\n }\n }\n }\n else {\n vnode.elm = api.createTextNode(vnode.text);\n }\n return vnode.elm;\n }\n function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {\n for (; startIdx <= endIdx; ++startIdx) {\n const ch = vnodes[startIdx];\n if (ch != null) {\n api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);\n }\n }\n }\n function invokeDestroyHook(vnode) {\n var _a, _b;\n const data = vnode.data;\n if (data !== undefined) {\n (_b = (_a = data === null || data === void 0 ? void 0 : data.hook) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a, vnode);\n for (let i = 0; i < cbs.destroy.length; ++i)\n cbs.destroy[i](vnode);\n if (vnode.children !== undefined) {\n for (let j = 0; j < vnode.children.length; ++j) {\n const child = vnode.children[j];\n if (child != null && typeof child !== 'string') {\n invokeDestroyHook(child);\n }\n }\n }\n }\n }\n function removeVnodes(parentElm, vnodes, startIdx, endIdx) {\n var _a, _b;\n for (; startIdx <= endIdx; ++startIdx) {\n let listeners;\n let rm;\n const ch = vnodes[startIdx];\n if (ch != null) {\n if (isDef(ch.sel)) {\n invokeDestroyHook(ch);\n listeners = cbs.remove.length + 1;\n rm = createRmCb(ch.elm, listeners);\n for (let i = 0; i < cbs.remove.length; ++i)\n cbs.remove[i](ch, rm);\n const removeHook = (_b = (_a = ch === null || ch === void 0 ? void 0 : ch.data) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.remove;\n if (isDef(removeHook)) {\n removeHook(ch, rm);\n }\n else {\n rm();\n }\n }\n else { // Text node\n api.removeChild(parentElm, ch.elm);\n }\n }\n }\n }\n function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {\n let oldStartIdx = 0;\n let newStartIdx = 0;\n let oldEndIdx = oldCh.length - 1;\n let oldStartVnode = oldCh[0];\n let oldEndVnode = oldCh[oldEndIdx];\n let newEndIdx = newCh.length - 1;\n let newStartVnode = newCh[0];\n let newEndVnode = newCh[newEndIdx];\n let oldKeyToIdx;\n let idxInOld;\n let elmToMove;\n let before;\n while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {\n if (oldStartVnode == null) {\n oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left\n }\n else if (oldEndVnode == null) {\n oldEndVnode = oldCh[--oldEndIdx];\n }\n else if (newStartVnode == null) {\n newStartVnode = newCh[++newStartIdx];\n }\n else if (newEndVnode == null) {\n newEndVnode = newCh[--newEndIdx];\n }\n else if (sameVnode(oldStartVnode, newStartVnode)) {\n patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);\n oldStartVnode = oldCh[++oldStartIdx];\n newStartVnode = newCh[++newStartIdx];\n }\n else if (sameVnode(oldEndVnode, newEndVnode)) {\n patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);\n oldEndVnode = oldCh[--oldEndIdx];\n newEndVnode = newCh[--newEndIdx];\n }\n else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right\n patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);\n api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));\n oldStartVnode = oldCh[++oldStartIdx];\n newEndVnode = newCh[--newEndIdx];\n }\n else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left\n patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);\n api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);\n oldEndVnode = oldCh[--oldEndIdx];\n newStartVnode = newCh[++newStartIdx];\n }\n else {\n if (oldKeyToIdx === undefined) {\n oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);\n }\n idxInOld = oldKeyToIdx[newStartVnode.key];\n if (isUndef(idxInOld)) { // New element\n api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);\n }\n else {\n elmToMove = oldCh[idxInOld];\n if (elmToMove.sel !== newStartVnode.sel) {\n api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);\n }\n else {\n patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);\n oldCh[idxInOld] = undefined;\n api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);\n }\n }\n newStartVnode = newCh[++newStartIdx];\n }\n }\n if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {\n if (oldStartIdx > oldEndIdx) {\n before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;\n addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);\n }\n else {\n removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);\n }\n }\n }\n function patchVnode(oldVnode, vnode, insertedVnodeQueue) {\n var _a, _b, _c, _d, _e;\n const hook = (_a = vnode.data) === null || _a === void 0 ? void 0 : _a.hook;\n (_b = hook === null || hook === void 0 ? void 0 : hook.prepatch) === null || _b === void 0 ? void 0 : _b.call(hook, oldVnode, vnode);\n const elm = vnode.elm = oldVnode.elm;\n const oldCh = oldVnode.children;\n const ch = vnode.children;\n if (oldVnode === vnode)\n return;\n if (vnode.data !== undefined) {\n for (let i = 0; i < cbs.update.length; ++i)\n cbs.update[i](oldVnode, vnode);\n (_d = (_c = vnode.data.hook) === null || _c === void 0 ? void 0 : _c.update) === null || _d === void 0 ? void 0 : _d.call(_c, oldVnode, vnode);\n }\n if (isUndef(vnode.text)) {\n if (isDef(oldCh) && isDef(ch)) {\n if (oldCh !== ch)\n updateChildren(elm, oldCh, ch, insertedVnodeQueue);\n }\n else if (isDef(ch)) {\n if (isDef(oldVnode.text))\n api.setTextContent(elm, '');\n addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);\n }\n else if (isDef(oldCh)) {\n removeVnodes(elm, oldCh, 0, oldCh.length - 1);\n }\n else if (isDef(oldVnode.text)) {\n api.setTextContent(elm, '');\n }\n }\n else if (oldVnode.text !== vnode.text) {\n if (isDef(oldCh)) {\n removeVnodes(elm, oldCh, 0, oldCh.length - 1);\n }\n api.setTextContent(elm, vnode.text);\n }\n (_e = hook === null || hook === void 0 ? void 0 : hook.postpatch) === null || _e === void 0 ? void 0 : _e.call(hook, oldVnode, vnode);\n }\n return function patch(oldVnode, vnode) {\n let i, elm, parent;\n const insertedVnodeQueue = [];\n for (i = 0; i < cbs.pre.length; ++i)\n cbs.pre[i]();\n if (!isVnode(oldVnode)) {\n oldVnode = emptyNodeAt(oldVnode);\n }\n if (sameVnode(oldVnode, vnode)) {\n patchVnode(oldVnode, vnode, insertedVnodeQueue);\n }\n else {\n elm = oldVnode.elm;\n parent = api.parentNode(elm);\n createElm(vnode, insertedVnodeQueue);\n if (parent !== null) {\n api.insertBefore(parent, vnode.elm, api.nextSibling(elm));\n removeVnodes(parent, [oldVnode], 0, 0);\n }\n }\n for (i = 0; i < insertedVnodeQueue.length; ++i) {\n insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);\n }\n for (i = 0; i < cbs.post.length; ++i)\n cbs.post[i]();\n return vnode;\n };\n}\n//# sourceMappingURL=init.js.map","export function vnode(sel, data, children, text, elm) {\n const key = data === undefined ? undefined : data.key;\n return { sel, data, children, text, elm, key };\n}\n//# sourceMappingURL=vnode.js.map","export const array = Array.isArray;\nexport function primitive(s) {\n return typeof s === 'string' || typeof s === 'number';\n}\n//# sourceMappingURL=is.js.map","function createElement(tagName) {\n return document.createElement(tagName);\n}\nfunction createElementNS(namespaceURI, qualifiedName) {\n return document.createElementNS(namespaceURI, qualifiedName);\n}\nfunction createTextNode(text) {\n return document.createTextNode(text);\n}\nfunction createComment(text) {\n return document.createComment(text);\n}\nfunction insertBefore(parentNode, newNode, referenceNode) {\n parentNode.insertBefore(newNode, referenceNode);\n}\nfunction removeChild(node, child) {\n node.removeChild(child);\n}\nfunction appendChild(node, child) {\n node.appendChild(child);\n}\nfunction parentNode(node) {\n return node.parentNode;\n}\nfunction nextSibling(node) {\n return node.nextSibling;\n}\nfunction tagName(elm) {\n return elm.tagName;\n}\nfunction setTextContent(node, text) {\n node.textContent = text;\n}\nfunction getTextContent(node) {\n return node.textContent;\n}\nfunction isElement(node) {\n return node.nodeType === 1;\n}\nfunction isText(node) {\n return node.nodeType === 3;\n}\nfunction isComment(node) {\n return node.nodeType === 8;\n}\nexport const htmlDomApi = {\n createElement,\n createElementNS,\n createTextNode,\n createComment,\n insertBefore,\n removeChild,\n appendChild,\n parentNode,\n nextSibling,\n tagName,\n setTextContent,\n getTextContent,\n isElement,\n isText,\n isComment,\n};\n//# sourceMappingURL=htmldomapi.js.map","import { vnode } from \"./vnode.js\";\nimport * as is from \"./is.js\";\nfunction addNS(data, children, sel) {\n data.ns = 'http://www.w3.org/2000/svg';\n if (sel !== 'foreignObject' && children !== undefined) {\n for (let i = 0; i < children.length; ++i) {\n const childData = children[i].data;\n if (childData !== undefined) {\n addNS(childData, children[i].children, children[i].sel);\n }\n }\n }\n}\nexport function h(sel, b, c) {\n var data = {};\n var children;\n var text;\n var i;\n if (c !== undefined) {\n if (b !== null) {\n data = b;\n }\n if (is.array(c)) {\n children = c;\n }\n else if (is.primitive(c)) {\n text = c;\n }\n else if (c && c.sel) {\n children = [c];\n }\n }\n else if (b !== undefined && b !== null) {\n if (is.array(b)) {\n children = b;\n }\n else if (is.primitive(b)) {\n text = b;\n }\n else if (b && b.sel) {\n children = [b];\n }\n else {\n data = b;\n }\n }\n if (children !== undefined) {\n for (i = 0; i < children.length; ++i) {\n if (is.primitive(children[i]))\n children[i] = vnode(undefined, undefined, undefined, children[i], undefined);\n }\n }\n if (sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&\n (sel.length === 3 || sel[3] === '.' || sel[3] === '#')) {\n addNS(data, children, sel);\n }\n return vnode(sel, data, children, text, undefined);\n}\n;\n//# sourceMappingURL=h.js.map","function updateClass(oldVnode, vnode) {\n var cur;\n var name;\n var elm = vnode.elm;\n var oldClass = oldVnode.data.class;\n var klass = vnode.data.class;\n if (!oldClass && !klass)\n return;\n if (oldClass === klass)\n return;\n oldClass = oldClass || {};\n klass = klass || {};\n for (name in oldClass) {\n if (oldClass[name] &&\n !Object.prototype.hasOwnProperty.call(klass, name)) {\n // was `true` and now not provided\n elm.classList.remove(name);\n }\n }\n for (name in klass) {\n cur = klass[name];\n if (cur !== oldClass[name]) {\n elm.classList[cur ? 'add' : 'remove'](name);\n }\n }\n}\nexport const classModule = { create: updateClass, update: updateClass };\n//# sourceMappingURL=class.js.map","const CAPS_REGEX = /[A-Z]/g;\nfunction updateDataset(oldVnode, vnode) {\n const elm = vnode.elm;\n let oldDataset = oldVnode.data.dataset;\n let dataset = vnode.data.dataset;\n let key;\n if (!oldDataset && !dataset)\n return;\n if (oldDataset === dataset)\n return;\n oldDataset = oldDataset || {};\n dataset = dataset || {};\n const d = elm.dataset;\n for (key in oldDataset) {\n if (!dataset[key]) {\n if (d) {\n if (key in d) {\n delete d[key];\n }\n }\n else {\n elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase());\n }\n }\n }\n for (key in dataset) {\n if (oldDataset[key] !== dataset[key]) {\n if (d) {\n d[key] = dataset[key];\n }\n else {\n elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key]);\n }\n }\n }\n}\nexport const datasetModule = { create: updateDataset, update: updateDataset };\n//# sourceMappingURL=dataset.js.map","function invokeHandler(handler, vnode, event) {\n if (typeof handler === 'function') {\n // call function handler\n handler.call(vnode, event, vnode);\n }\n else if (typeof handler === 'object') {\n // call multiple handlers\n for (var i = 0; i < handler.length; i++) {\n invokeHandler(handler[i], vnode, event);\n }\n }\n}\nfunction handleEvent(event, vnode) {\n var name = event.type;\n var on = vnode.data.on;\n // call event handler(s) if exists\n if (on && on[name]) {\n invokeHandler(on[name], vnode, event);\n }\n}\nfunction createListener() {\n return function handler(event) {\n handleEvent(event, handler.vnode);\n };\n}\nfunction updateEventListeners(oldVnode, vnode) {\n var oldOn = oldVnode.data.on;\n var oldListener = oldVnode.listener;\n var oldElm = oldVnode.elm;\n var on = vnode && vnode.data.on;\n var elm = (vnode && vnode.elm);\n var name;\n // optimization for reused immutable handlers\n if (oldOn === on) {\n return;\n }\n // remove existing listeners which no longer used\n if (oldOn && oldListener) {\n // if element changed or deleted we remove all existing listeners unconditionally\n if (!on) {\n for (name in oldOn) {\n // remove listener if element was changed or existing listeners removed\n oldElm.removeEventListener(name, oldListener, false);\n }\n }\n else {\n for (name in oldOn) {\n // remove listener if existing listener removed\n if (!on[name]) {\n oldElm.removeEventListener(name, oldListener, false);\n }\n }\n }\n }\n // add new listeners which has not already attached\n if (on) {\n // reuse existing listener or create new\n var listener = vnode.listener = oldVnode.listener || createListener();\n // update vnode for listener\n listener.vnode = vnode;\n // if element changed or added we add all needed listeners unconditionally\n if (!oldOn) {\n for (name in on) {\n // add listener if element was changed or new listeners added\n elm.addEventListener(name, listener, false);\n }\n }\n else {\n for (name in on) {\n // add listener if new listener added\n if (!oldOn[name]) {\n elm.addEventListener(name, listener, false);\n }\n }\n }\n }\n}\nexport const eventListenersModule = {\n create: updateEventListeners,\n update: updateEventListeners,\n destroy: updateEventListeners\n};\n//# sourceMappingURL=eventlisteners.js.map","// Bindig `requestAnimationFrame` like this fixes a bug in IE/Edge. See #360 and #409.\nvar raf = (typeof window !== 'undefined' && (window.requestAnimationFrame).bind(window)) || setTimeout;\nvar nextFrame = function (fn) {\n raf(function () {\n raf(fn);\n });\n};\nvar reflowForced = false;\nfunction setNextFrame(obj, prop, val) {\n nextFrame(function () {\n obj[prop] = val;\n });\n}\nfunction updateStyle(oldVnode, vnode) {\n var cur;\n var name;\n var elm = vnode.elm;\n var oldStyle = oldVnode.data.style;\n var style = vnode.data.style;\n if (!oldStyle && !style)\n return;\n if (oldStyle === style)\n return;\n oldStyle = oldStyle || {};\n style = style || {};\n var oldHasDel = 'delayed' in oldStyle;\n for (name in oldStyle) {\n if (!style[name]) {\n if (name[0] === '-' && name[1] === '-') {\n elm.style.removeProperty(name);\n }\n else {\n elm.style[name] = '';\n }\n }\n }\n for (name in style) {\n cur = style[name];\n if (name === 'delayed' && style.delayed) {\n for (const name2 in style.delayed) {\n cur = style.delayed[name2];\n if (!oldHasDel || cur !== oldStyle.delayed[name2]) {\n setNextFrame(elm.style, name2, cur);\n }\n }\n }\n else if (name !== 'remove' && cur !== oldStyle[name]) {\n if (name[0] === '-' && name[1] === '-') {\n elm.style.setProperty(name, cur);\n }\n else {\n elm.style[name] = cur;\n }\n }\n }\n}\nfunction applyDestroyStyle(vnode) {\n var style;\n var name;\n var elm = vnode.elm;\n var s = vnode.data.style;\n if (!s || !(style = s.destroy))\n return;\n for (name in style) {\n elm.style[name] = style[name];\n }\n}\nfunction applyRemoveStyle(vnode, rm) {\n var s = vnode.data.style;\n if (!s || !s.remove) {\n rm();\n return;\n }\n if (!reflowForced) {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n vnode.elm.offsetLeft;\n reflowForced = true;\n }\n var name;\n var elm = vnode.elm;\n var i = 0;\n var compStyle;\n var style = s.remove;\n var amount = 0;\n var applied = [];\n for (name in style) {\n applied.push(name);\n elm.style[name] = style[name];\n }\n compStyle = getComputedStyle(elm);\n var props = compStyle['transition-property'].split(', ');\n for (; i < props.length; ++i) {\n if (applied.indexOf(props[i]) !== -1)\n amount++;\n }\n elm.addEventListener('transitionend', function (ev) {\n if (ev.target === elm)\n --amount;\n if (amount === 0)\n rm();\n });\n}\nfunction forceReflow() {\n reflowForced = false;\n}\nexport const styleModule = {\n pre: forceReflow,\n create: updateStyle,\n update: updateStyle,\n destroy: applyDestroyStyle,\n remove: applyRemoveStyle\n};\n//# sourceMappingURL=style.js.map","/**\n * Utility library for manipulation of JSON data.\n *\n * Main characteristics:\n * - input/output data types are limited to JSON data, functions and\n * `undefined` (sparse arrays and complex objects with prototype chain are\n * not supported)\n * - functional API with curried functions (similar to ramdajs)\n * - implementation based on natively supported browser JS API\n * - scope limited to most used functions in hat projects\n * - usage of `paths` instead of `lenses`\n *\n * TODO: define convetion for naming arguments based on their type and\n * semantics\n *\n * @module @hat-core/util\n */\n\n/**\n * Path can be an object property name, array index, or array of Paths\n *\n * TODO: explain paths and path compositions (include examples)\n *\n * @typedef {(String|Number|Path[])} module:@hat-core/util.Path\n */\n\n/**\n * Identity function returning same value provided as argument.\n *\n * @function\n * @sig a -> a\n * @param {*} x input value\n * @return {*} same value as input\n */\nexport const identity = x => x;\n\n/**\n * Check if value is `null` or `undefined`.\n *\n * For same argument, if this function returns `true`, functions `isBoolean`,\n * `isInteger`, `isNumber`, `isString`, `isArray` and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nexport const isNil = x => x == null;\n\n/**\n * Check if value is Boolean.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isInteger`, `isNumber`, `isString`, `isArray` and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nexport const isBoolean = x => typeof(x) == 'boolean';\n\n/**\n * Check if value is Integer.\n *\n * For same argument, if this function returns `true`, function `isNumber` will\n * also return `true`.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isString`, `isArray` and `isObject` will return `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @type {Boolean}\n */\nexport const isInteger = Number.isInteger;\n\n/**\n * Check if value is Number.\n *\n * For same argument, if this function returns `true`, function `isInteger` may\n * also return `true` if argument is integer number.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isString`, `isArray` and `isObject` will return `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nexport const isNumber = x => typeof(x) == 'number';\n\n/**\n * Check if value is String.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isInteger`, `isNumber`, `isArray`, and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {Any} x input value\n * @type {Boolean}\n */\nexport const isString = x => typeof(x) == 'string';\n\n/**\n * Check if value is Array.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isInteger`, `isNumber`, `isString`, and `isObject` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nexport const isArray = Array.isArray;\n\n/**\n * Check if value is Object.\n *\n * For same argument, if this function returns `true`, functions `isNil`,\n * `isBoolean`, `isInteger`, `isNumber`, `isString`, and `isArray` will return\n * `false`.\n *\n * @function\n * @sig * -> Boolean\n * @param {*} x input value\n * @return {Boolean}\n */\nexport const isObject = x => typeof(x) == 'object' &&\n !isArray(x) &&\n !isNil(x);\n\n/**\n * Strictly parse integer from string\n *\n * If provided string doesn't represent integer value, `NaN` is returned.\n *\n * @function\n * @sig String -> Number\n * @param {String} value\n * @return {Number}\n */\nexport function strictParseInt(value) {\n if (/^(-|\\+)?([0-9]+)$/.test(value))\n return Number(value);\n return NaN;\n}\n\n/**\n * Strictly parse floating point number from string\n *\n * If provided string doesn't represent valid number, `NaN` is returned.\n *\n * @function\n * @sig String -> Number\n * @param {String} value\n * @return {Number}\n */\nexport function strictParseFloat(value) {\n if (/^(-|\\+)?([0-9]+(\\.[0-9]+)?)$/.test(value))\n return Number(value);\n return NaN;\n}\n\n/**\n * Create new deep copy of input value.\n *\n * In case of Objects or Arrays, new instances are created with elements\n * obtained by recursivly calling `clone` in input argument values.\n *\n * @function\n * @sig * -> *\n * @param {*} x value\n * @return {*} copy of value\n */\nexport function clone(x) {\n if (isArray(x))\n return Array.from(x, clone);\n if (isObject(x)) {\n let ret = {};\n for (let i in x)\n ret[i] = clone(x[i]);\n return ret;\n }\n return x;\n}\n\n/**\n * Combine two arrays in single array of pairs\n *\n * The returned array is truncated to the length of the shorter of the two\n * input arrays.\n *\n * @function\n * @sig [a] -> [b] -> [[a,b]]\n * @param {Array} arr1\n * @param {Array} arr2\n * @return {Array}\n */\nexport function zip(arr1, arr2) {\n return Array.from((function*() {\n for (let i = 0; i < arr1.length || i < arr2.length; ++i)\n yield [arr1[i], arr2[i]];\n })());\n}\n\n/**\n * Convert object to array of key, value pairs\n *\n * @function\n * @sig Object -> [[String,*]]\n * @param {Object} obj\n * @return {Array}\n */\nexport function toPairs(obj) {\n return Object.entries(obj);\n}\n\n/**\n * Convert array of key, value pairs to object\n *\n * @function\n * @sig [[String,*]] -> Object\n * @param {Array} arr\n * @return {Object}\n */\nexport function fromPairs(arr) {\n let ret = {};\n for (let [k, v] of arr)\n ret[k] = v;\n return ret;\n}\n\n/**\n * Flatten nested arrays.\n *\n * Create array with same elements as in input array where all elements which\n * are also arrays are replaced with elements of resulting recursive\n * application of flatten function.\n *\n * @function\n * @sig [a] -> [b]\n * @param {Array} arr\n * @return {Array}\n */\nexport function flatten(arr) {\n return Array.from((function* flatten(x) {\n if (isArray(x)) {\n for (let i of x)\n yield* flatten(i);\n } else {\n yield x;\n }\n })(arr));\n}\n\n/**\n * Pipe function calls\n *\n * Pipe provides functional composition with reversed order. First function\n * may have any arity and all other functions are called with only single\n * argument (result from previous function application).\n *\n * In case when no function is provided, pipe returns identity function.\n *\n * @function\n * @sig (((a1, a2, ..., an) -> b1), (b1 -> b2), ..., (bm1 -> bm)) -> ((a1, a2, ..., an) -> bm)\n * @param {...Function} fns functions\n * @return {Function}\n */\nexport function pipe(...fns) {\n if (fns.length < 1)\n return identity;\n return function (...args) {\n let ret = fns[0].apply(this, args);\n for (let fn of fns.slice(1))\n ret = fn(ret);\n return ret;\n };\n}\n\n/**\n * Apply list of functions to same arguments and return list of results\n *\n * @function\n * @sig ((a1 -> ... -> an -> b1), ..., (a1 -> ... -> an -> bm)) -> (a1 -> ... -> an -> [b1,...,bm])\n * @param {...Function} fns functions\n * @return {Function}\n */\nexport function flap(...fns) {\n return (...args) => fns.map(fn => fn.apply(this, args));\n}\n\n/**\n * Curry function with fixed arguments lenth\n *\n * Function arity is determined based on function's length property.\n *\n * @function\n * @sig (* -> a) -> (* -> a)\n * @param {Function} fn\n * @return {Function}\n */\nexport function curry(fn) {\n let wrapper = function(oldArgs) {\n return function(...args) {\n args = oldArgs.concat(args);\n if (args.length >= fn.length)\n return fn(...args);\n return wrapper(args);\n };\n };\n return wrapper([]);\n}\n\n/**\n * Deep object equality\n * (curried function)\n *\n * @function\n * @sig a -> b -> Boolean\n * @param {*} x\n * @param {*} y\n * @return {Boolean}\n */\nexport const equals = curry((x, y) => {\n if (x === y)\n return true;\n if (typeof(x) != 'object' ||\n typeof(y) != 'object' ||\n x === null ||\n y === null)\n return false;\n if (Array.isArray(x) && Array.isArray(y)) {\n if (x.length != y.length)\n return false;\n for (let [a, b] of zip(x, y)) {\n if (!equals(a, b))\n return false;\n }\n return true;\n } else if (!Array.isArray(x) && !Array.isArray(y)) {\n if (Object.keys(x).length != Object.keys(y).length)\n return false;\n for (let key in x) {\n if (!(key in y))\n return false;\n }\n for (let key in x) {\n if (!equals(x[key], y[key]))\n return false;\n }\n return true;\n }\n return false;\n});\n\n\n/**\n * Create array by repeating same value\n * (curried function)\n *\n * @function\n * @sig a -> Number -> [a]\n * @param {*} x\n * @param {Number} n\n * @return {Array}\n */\nexport const repeat = curry((x, n) => Array.from({length: n}, _ => x));\n\n/**\n * Get value referenced by path\n * (curried function)\n *\n * If input value doesn't contain provided path value, `undefined` is returned.\n *\n * @function\n * @sig Path -> a -> b\n * @param {Path} path\n * @param {*} x\n * @return {*}\n */\nexport const get = curry((path, x) => {\n let ret = x;\n for (let i of flatten(path)) {\n if (ret === null || typeof(ret) != 'object')\n return undefined;\n ret = ret[i];\n }\n return ret;\n});\n\n/**\n * Change value referenced with path by appling function\n * (curried function)\n *\n * @function\n * @sig Path -> (a -> b) -> c -> c\n * @param {Path} path\n * @param {Function} fn\n * @param {*} x\n * @return {*}\n */\nexport const change = curry((path, fn, x) => {\n return (function change(path, x) {\n if (path.length < 1)\n return fn(x);\n const [first, ...rest] = path;\n if (isInteger(first)) {\n x = (isArray(x) ? Array.from(x) : repeat(undefined, first));\n } else if (isString(first)) {\n x = (isObject(x) ? Object.assign({}, x) : {});\n } else {\n throw 'invalid path';\n }\n x[first] = change(rest, x[first]);\n return x;\n })(flatten(path), x);\n});\n\n/**\n * Replace value referenced with path with another value\n * (curried function)\n *\n * @function\n * @sig Path -> (a -> b) -> c -> c\n * @param {Path} path\n * @param {*} val\n * @param {*} x\n * @return {*}\n */\nexport const set = curry((path, val, x) => change(path, _ => val, x));\n\n/**\n * Omitting value referenced by path\n * (curried function)\n *\n * @function\n * @sig Path -> a -> a\n * @param {Path} path\n * @param {*} x\n * @return {*}\n */\nexport const omit = curry((path, x) => {\n function _omit(path, x) {\n if (isInteger(path[0])) {\n x = (isArray(x) ? Array.from(x) : []);\n } else if (isString(path[0])) {\n x = (isObject(x) ? Object.assign({}, x) : {});\n } else {\n throw 'invalid path';\n }\n if (path.length > 1) {\n x[path[0]] = _omit(path.slice(1), x[path[0]]);\n } else if (isInteger(path[0])) {\n x.splice(path[0], 1);\n } else {\n delete x[path[0]];\n }\n return x;\n }\n path = flatten(path);\n if (path.length < 1)\n return undefined;\n return _omit(path, x);\n});\n\n/**\n * Change by moving value from source path to destination path\n * (curried function)\n *\n * @function\n * @sig Path -> Path -> a -> a\n * @param {Path} srcPath\n * @param {Path} dstPath\n * @param {*} x\n * @return {*}\n */\nexport const move = curry((srcPath, dstPath, x) => pipe(\n set(dstPath, get(srcPath, x)),\n omit(srcPath)\n)(x));\n\n/**\n * Sort array\n * (curried function)\n *\n * Comparison function receives two arguments representing array elements and\n * should return:\n * - negative number in case first argument is more significant then second\n * - zero in case first argument is equaly significant as second\n * - positive number in case first argument is less significant then second\n *\n * @function\n * @sig ((a, a) -> Number) -> [a] -> [a]\n * @param {Function} fn\n * @param {Array} arr\n * @return {Array}\n */\nexport const sort = curry((fn, arr) => Array.from(arr).sort(fn));\n\n/**\n * Sort array based on results of appling function to it's elements\n * (curried function)\n *\n * Resulting order is determined by comparring function application results\n * with greater then and lesser then operators.\n *\n * @function\n * @sig (a -> b) -> [a] -> [a]\n * @param {Function} fn\n * @param {Array} arr\n * @return {Array}\n */\nexport const sortBy = curry((fn, arr) => sort((x, y) => {\n const xVal = fn(x);\n const yVal = fn(y);\n if (xVal < yVal)\n return -1;\n if (xVal > yVal)\n return 1;\n return 0;\n}, arr));\n\n/**\n * Create object containing only subset of selected properties\n * (curried function)\n *\n * @function\n * @sig [String] -> a -> a\n * @param {Array} arr\n * @param {Object} obj\n * @return {Object}\n */\nexport const pick = curry((arr, obj) => {\n const ret = {};\n for (let i of arr)\n if (i in obj)\n ret[i] = obj[i];\n return ret;\n});\n\n/**\n * Change array or object by appling function to it's elements\n * (curried function)\n *\n * For each element, provided function is called with element value,\n * index/key and original container.\n *\n * @function\n * @sig ((a, Number, [a]) -> b) -> [a] -> [b]\n * @sig ((a, String, {String: a}) -> b) -> {String: a} -> {String: b}\n * @param {Function} fn\n * @param {Array|Object} x\n * @return {Array|Object}\n */\nexport const map = curry((fn, x) => {\n if (isArray(x))\n return x.map(fn);\n const res = {};\n for (let k in x)\n res[k] = fn(x[k], k, x);\n return res;\n});\n\n/**\n * Change array to contain only elements for which function returns `true`\n * (curried function)\n *\n * @function\n * @sig (a -> Boolean) -> [a] -> [a]\n * @param {Function} fn\n * @param {Array} arr\n * @return {Array}\n */\nexport const filter = curry((fn, arr) => arr.filter(fn));\n\n/**\n * Append value to end of array\n * (curried function)\n *\n * @function\n * @sig a -> [a] -> [a]\n * @param {*} val\n * @param {Array} arr\n * @return {Array}\n */\nexport const append = curry((val, arr) => arr.concat([val]));\n\n/**\n * Reduce array values by appling function\n * (curried function)\n *\n * For each array element, provided function is called with accumulator,\n * current value, current index and source array.\n *\n * TODO: support objects\n *\n * @function\n * @sig ((b, a, Number, [a]) -> b) -> b -> [a] -> b\n * @param {Function} fn\n * @param {*} val initial accumulator value\n * @param {Array} arr\n * @return {*} reduced value\n */\nexport const reduce = curry((fn, val, arr) => arr.reduce(fn, val));\n\n/**\n * Merge two objects\n * (curried function)\n *\n * If same property exist in both arguments, second argument's value is used\n * as resulting value\n *\n * @function\n * @sig a -> a -> a\n * @param {Object} x\n * @param {Object} y\n * @return {Object}\n */\nexport const merge = curry((x, y) => Object.assign({}, x, y));\n\n/**\n * Merge multiple objects\n * (curried function)\n *\n * If same property exist in multiple arguments, value from the last argument\n * containing that property is used\n *\n * @function\n * @sig [a] -> a\n * @param {Object[]}\n * @return {Object}\n */\nexport const mergeAll = reduce(merge, {});\n\n/**\n * Find element in array or object for which provided function returns `true`\n * (curried function)\n *\n * Until element is found, provided function is called for each element with\n * arguments: current element, current index/key and initial container.\n *\n * If searched element is not found, `undefined` is returned.\n *\n * @function\n * @sig ((a, Number, [a]) -> Boolean) -> [a] -> a\n * @sig ((a, String, {String: a}) -> Boolean) -> {String: a} -> a\n * @param {Function} fn\n * @param {Array|Object} x\n * @return {*}\n */\nexport const find = curry((fn, x) => {\n if (isArray(x))\n return x.find(fn);\n for (let k in x)\n if (fn(x[k], k, x))\n return x[k];\n});\n\n/**\n * Find element's index/key in array or object for which provided function\n * returns `true`\n * (curried function)\n *\n * Until element is found, provided function is called for each element with\n * arguments: current element, current index/key and initial container.\n *\n * If searched element is not found, `undefined` is returned.\n *\n * @function\n * @sig ((a, Number, [a]) -> Boolean) -> [a] -> a\n * @sig ((a, String, {String: a}) -> Boolean) -> {String: a} -> a\n * @param {Function} fn\n * @param {Array|Object} x\n * @return {*}\n */\nexport const findIndex = curry((fn, x) => {\n if (isArray(x))\n return x.findIndex(fn);\n for (let k in x)\n if (fn(x[k], k, x))\n return k;\n});\n\n/**\n * Concatenate two arrays\n * (curried function)\n *\n * @function\n * @sig [a] -> [a] -> [a]\n * @param {Array} x\n * @param {Array} y\n * @return {Array}\n */\nexport const concat = curry((x, y) => x.concat(y));\n\n/**\n * Create union of two arrays using `equals` to check equality\n * (curried function)\n *\n * @function\n * @sig [a] -> [a] -> [a]\n * @param {Array} x\n * @param {Array} y\n * @return {Array}\n */\nexport const union = curry((x, y) => {\n return reduce((acc, val) => {\n if (!find(equals(val), x))\n acc = append(val, acc);\n return acc;\n }, x, y);\n});\n\n/**\n * Check if array contains value\n * (curried function)\n *\n * TODO: add support for objects (should we check for keys or values?)\n *\n * @function\n * @sig a -> [a] -> Boolean\n * @param {*} val\n * @param {Array|Object} x\n * @return {Boolean}\n */\nexport const contains = curry((val, arr) => arr.includes(val));\n\n/**\n * Insert value into array on specified index\n * (curried function)\n *\n * @function\n * @sig Number -> a -> [a] -> [a]\n * @param {Number} idx\n * @param {*} val\n * @param {Array} arr\n * @return {Array}\n */\nexport const insert = curry((idx, val, arr) =>\n arr.slice(0, idx).concat([val], arr.slice(idx)));\n\n/**\n * Get array slice\n * (curried function)\n *\n * @function\n * @sig Number -> Number -> [a] -> [a]\n * @param {Number} begin\n * @param {Number} end\n * @param {Array} arr\n * @return {Array}\n */\nexport const slice = curry((begin, end, arr) => arr.slice(begin, end));\n\n/**\n * Reverse array\n *\n * @function\n * @sig [a] -> [a]\n * @param {Array} arr\n * @return {Array}\n */\nexport function reverse(arr) {\n return Array.from(arr).reverse();\n}\n\n/**\n * Array length\n *\n * @function\n * @sig [a] -> Number\n * @param {Array} arr\n * @return {Number}\n */\nexport function length(arr) {\n return arr.length;\n}\n\n/**\n * Increment value\n * @param {Number} val\n * @return {Number}\n */\nexport function inc(val) {\n return val + 1;\n}\n\n/**\n * Decrement value\n * @param {Number} val\n * @return {Number}\n */\nexport function dec(val) {\n return val - 1;\n}\n\n/**\n * Logical not\n * @param {Any} val\n * @return {Boolean}\n */\nexport function not(val) {\n return !val;\n}\n\n/**\n * Create promise that resolves in `t` milliseconds\n *\n * TODO: move to other module\n *\n * @function\n * @sig Number -> Promise\n * @param {Number} t\n * @return {Promise}\n */\nexport function sleep(t) {\n return new Promise(resolve => {\n setTimeout(() => { resolve(); }, t);\n });\n}\n\n/**\n * Delay function call `fn(...args)` for `t` milliseconds\n *\n * TODO: move to other module\n *\n * @function\n * @sig (((a1, a2, ..., an) -> _), Number, a1, a2, ..., an) -> Promise\n * @param {Function} fn\n * @param {Number} [t=0]\n * @param {*} args\n * @return {Promise}\n */\nexport function delay(fn, t, ...args) {\n return new Promise(resolve => {\n setTimeout(() => { resolve(fn(...args)); }, t || 0);\n });\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import r from '@hat-core/renderer';\n\n\nexport async function init() {\n await r.set('view', {});\n}\n\n\nexport function vt() {\n return ['div', 'HELLO WORLD']\n}\n\n\nexport function destroy() {}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/examples/0002/view/login/dummy b/examples/0002/view/login/dummy new file mode 100644 index 0000000..e69de29 diff --git a/examples/0003/.gitignore b/examples/0003/.gitignore new file mode 100644 index 0000000..3395e62 --- /dev/null +++ b/examples/0003/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +build/ +data/ +dataset/ +.venv +__pycache__/ + +.ipynb_checkpoints/ diff --git a/examples/0003/Dockerfile b/examples/0003/Dockerfile new file mode 100644 index 0000000..a4c7a44 --- /dev/null +++ b/examples/0003/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.12 + +RUN apt update +RUN apt install -y npm +RUN npm install -g yarn + +WORKDIR /opt/aimm + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY package.json . +COPY webpack.config.js . + +COPY install.sh . +RUN ./install.sh + +COPY conf ./conf +COPY src_py ./src_py +COPY src_js ./src_js +COPY src_scss ./src_scss + +COPY run.sh . + +CMD ["./run.sh"] diff --git a/examples/0003/README.md b/examples/0003/README.md new file mode 100644 index 0000000..5ddc342 --- /dev/null +++ b/examples/0003/README.md @@ -0,0 +1,67 @@ +# hat-aimm-anomaly +Hat aimm anomaly example. + +![GUI screenshot](docs/scr.png) + +## Use + +To run the example on your machine, install all of the pip dependencies and run +`install.sh`. There is also a Dockerfile available, that can be used to generate a +docker image and use it to run the example. The example uses ports 23020 to 23023, where +the main GUI app is on 23023 (credentials are `user` and `pass`). + + +## Implementation + + +### DataFlow + +[```AnomalyModule```](src_py/air_supervision/modules/controller/anomaly.py) and +[```ForecastModule```](src_py/air_supervision/modules/controller/forecast.py) +run in parallel. Both are inherited from +[```GenericReadingsModule```](src_py/air_supervision/modules/controller.py). + +Both recieve new data from the device with event types: ```('gui', 'system', +'timeseries', 'reading')```. The received data is being saved in their +```ReadingsHandler```'s (objects that handle when that data should be +propagated to the adapter). + +When the event from adapter with a type ```('back_action', 'forecast', '*')``` +or ```('back_action', 'anomaly', '*')``` is read, that module will run +appopriate function to deal with that action. + +Types of actions you can expect from the adapter: + +* ```setting_change``` +* ```model_change``` + +In case of an event with type = ```('back_action', '*', 'model_change')```, +```Controller``` create a new model instance of a class with the name +```'model'``` in that event dict. (for example +```event.payload.data['model']``` is in that case == ```'SVM'``` and class +'SVM' exists in anomaly_model.py). + +Every Controller module has its own dicts of model objects and +depending on later back_action events, that dicts gets appended with new models. +If a model of same type is again registered, that won't create a new model, it +will just label old one as the current. (only one can be the current model in +both modules) + +For example ```ForecastModule``` has the following models generated: (so when +action create/fit/predict is called, it will be called upon current model) +```py +models = { + 'MultiOutputSVR': *MultiOutputSVR OBJECT*, + 'linear': *linear OBJECT* +} +current_model = 'linear' +``` + +When a model is created, module sends a message to AIMM module to create that +model with a same name in backend. When we get confim message, we fit that +model and prepare data for prediction process. + +If we get a setting_change event,it is expected from the module to update +hyperparameters of the current model. (means that a user changes some of that +and wants the model to be re-fitted). In that case, we re-fit the current model +with new parameters. diff --git a/examples/0003/conf/aimm.yaml b/examples/0003/conf/aimm.yaml new file mode 100644 index 0000000..9526a4c --- /dev/null +++ b/examples/0003/conf/aimm.yaml @@ -0,0 +1,44 @@ +--- +name: aimm +hat: + eventer_server: + host: 127.0.0.1 + port: 23012 +engine: + sigterm_timeout: 5 + max_children: 5 + check_children_period: 3 +backend: + module: aimm.server.backend.sqlite + path: data/aimm.db +control: + - module: aimm.server.control.event + event_prefixes: + predict: ['aimm', 'predict'] + create_instance: ['aimm', 'create_instance'] + fit: ['aimm', 'fit'] + state_event_type: ['aimm', 'state'] + action_state_event_type: ['aimm', 'action'] +plugins: + names: + - 'air_supervision.aimm.anomaly' + - 'air_supervision.aimm.forecast' +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + host: 127.0.0.1 + port: 6514 + comm_type: TCP + level: INFO + formatter: default + queue_size: 50 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0003/conf/event.yaml b/examples/0003/conf/event.yaml new file mode 100644 index 0000000..f53dd5e --- /dev/null +++ b/examples/0003/conf/event.yaml @@ -0,0 +1,73 @@ +--- +name: event +server_id: 0 +backend: + db_path: data/event.db + identifier: null + module: hat.event.backends.lmdb + flush_period: 5 + cleanup_period: 5 + conditions: [] + latest: + subscriptions: + - ['*'] + timeseries: [] +modules: + - module: air_supervision.modules.readings + - module: air_supervision.modules.controller + model_family: anomaly + batch_size: 48 + min_readings: 24 + models: + SVM: + contamination: 0.3 + svm1: 1 + svm2: 2 + Cluster: + contamination: 0.3 + cluster1: 1 + cluster2: 3 + Forest: + contamination: 0.3 + other_test_p: 1 + third: 4 + - module: air_supervision.modules.controller + model_family: forecast + batch_size: 48 + min_readings: 24 + models: + MultiOutputSVR: + C: 2000 + svm1: 1 + svm2: 2 + Linear: + contamination: 0.3 + cluster1: 1 + cluster2: 3 + Constant: + contamination: 0.3 + other_test_p: 1 + third: 4 + - module: air_supervision.modules.enable_all +eventer_server: + host: 127.0.0.1 + port: 23012 +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + host: 127.0.0.1 + port: 6514 + comm_type: TCP + level: INFO + formatter: default + queue_size: 50 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0003/conf/gateway.yaml b/examples/0003/conf/gateway.yaml new file mode 100644 index 0000000..396adb9 --- /dev/null +++ b/examples/0003/conf/gateway.yaml @@ -0,0 +1,29 @@ +--- +name: gateway +event_server: + eventer_server: + host: 127.0.0.1 + port: 23012 +devices: + - module: air_supervision.devices.air_readings + name: device + dataset_path: dataset/ambient_temperature_system_failure.csv +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + host: 127.0.0.1 + port: 6514 + comm_type: TCP + level: INFO + formatter: default + queue_size: 50 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0003/conf/gui.yaml b/examples/0003/conf/gui.yaml new file mode 100644 index 0000000..ddeb3e2 --- /dev/null +++ b/examples/0003/conf/gui.yaml @@ -0,0 +1,47 @@ +--- +name: gui +event_server: + eventer_server: + host: 127.0.0.1 + port: 23012 +address: + host: 0.0.0.0 + port: 23023 +adapters: + - module: air_supervision.adapters.timeseries + name: timeseries +views: + - name: login + builtin: login + conf: null + - name: main + view_path: build/views/main + conf: null +initial_view: login +users: + - name: user + password: + hash: cef3cf37b4fa2a692f06d6e637e112bd3a37179a8c6752c115ff21813a816574 + salt: 497b53087260002cd62ffabc94267437 + roles: + - user + view: main +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + host: 127.0.0.1 + port: 6514 + comm_type: TCP + level: INFO + formatter: default + queue_size: 50 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0003/conf/orchestrator.yaml b/examples/0003/conf/orchestrator.yaml new file mode 100644 index 0000000..6c1415f --- /dev/null +++ b/examples/0003/conf/orchestrator.yaml @@ -0,0 +1,80 @@ +--- +type: orchestrator +ui: + host: 0.0.0.0 + port: 23021 +components: +- args: + - hat-syslog-server + - --db-path + - ./data/syslog.db + delay: 0 + name: syslog + revive: false + start_delay: 1 + create_timeout: 5 + sigint_timeout: 2 + sigkill_timeout: 3 +- args: + - hat-event-server + - --conf + - ./conf/event.yaml + delay: 1 + name: event + revive: false + start_delay: 0.5 + create_timeout: 2 + sigint_timeout: 2 + sigkill_timeout: 2 +- args: + - hat-gateway + - --conf + - ./conf/gateway.yaml + delay: 2 + name: gateway + revive: false + start_delay: 0.5 + create_timeout: 2 + sigint_timeout: 2 + sigkill_timeout: 2 +- args: + - hat-gui-server + - --conf + - ./conf/gui.yaml + delay: 3 + name: gui + revive: false + start_delay: 0.5 + create_timeout: 2 + sigint_timeout: 2 + sigkill_timeout: 2 +- args: + - aimm-server + - --conf + - ./conf/aimm.yaml + delay: 3 + name: aimm + revive: false + start_delay: 0.5 + create_timeout: 2 + sigint_timeout: 2 + sigkill_timeout: 2 +log: + disable_existing_loggers: false + formatters: + default: {} + handlers: + syslog: + class: hat.syslog.handler.SysLogHandler + host: 127.0.0.1 + port: 6514 + comm_type: TCP + level: INFO + formatter: default + queue_size: 50 + root: + handlers: + - syslog + level: INFO + version: 1 +... diff --git a/examples/0003/docs/UML.png b/examples/0003/docs/UML.png new file mode 100644 index 0000000..e93e642 Binary files /dev/null and b/examples/0003/docs/UML.png differ diff --git a/examples/0003/docs/scr.png b/examples/0003/docs/scr.png new file mode 100644 index 0000000..f4938d0 Binary files /dev/null and b/examples/0003/docs/scr.png differ diff --git a/examples/0003/install.sh b/examples/0003/install.sh new file mode 100755 index 0000000..0264e8b --- /dev/null +++ b/examples/0003/install.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +mkdir -p dataset +curl https://raw.githubusercontent.com/numenta/NAB/master/data/realKnownCause/ambient_temperature_system_failure.csv --output dataset/ambient_temperature_system_failure.csv --silent +yarn install --silent diff --git a/examples/0003/notebooks/Forecast.ipynb b/examples/0003/notebooks/Forecast.ipynb new file mode 100644 index 0000000..ee193b6 --- /dev/null +++ b/examples/0003/notebooks/Forecast.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 8, + "id": "3f16cec5", + "metadata": {}, + "outputs": [], + "source": [ + "import csv\n", + "import numpy\n", + "\n", + "values = []\n", + "\n", + "with open(\"dataset/ambient_temperature_system_failure.csv\", \"r\") as f:\n", + " reader = csv.reader(f, delimiter=\"\\t\")\n", + " for i, line in enumerate(reader):\n", + " if not i:\n", + " continue\n", + " value = float(line[0].split(',')[1])\n", + " value = (float(value) - 32) * 5 / 9\n", + "\n", + " values.append(value)\n", + "\n", + "x, y = [], []\n", + "for i in range(48, len(values) - 24, 24):\n", + " x.append(values[i - 48:i])\n", + " y.append(values[i:i + 24])\n", + "\n", + "x, y = numpy.array(x), numpy.array(y)\n", + "\n", + "fit_start = int(len(x) * 0.2)\n", + "x_fit, y_fit = x[fit_start:], y[fit_start:]\n", + "x_valid, y_valid = x[:fit_start], y[:fit_start]" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "a13ad51e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
GridSearchCV(estimator=MultiOutputRegressor(estimator=SVR()),\n",
+       "             param_grid={'estimator__C': [6, 7, 8]},\n",
+       "             scoring=make_scorer(score, greater_is_better=False))
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ], + "text/plain": [ + "GridSearchCV(estimator=MultiOutputRegressor(estimator=SVR()),\n", + " param_grid={'estimator__C': [6, 7, 8]},\n", + " scoring=make_scorer(score, greater_is_better=False))" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import sklearn.model_selection\n", + "import sklearn.svm\n", + "import sklearn.multioutput\n", + "import sklearn.metrics\n", + "\n", + "def score(*args, **kwargs):\n", + " return sklearn.metrics.mean_squared_error(*args, squared=False, **kwargs)\n", + "\n", + "\n", + "search = sklearn.model_selection.GridSearchCV(\n", + " sklearn.multioutput.MultiOutputRegressor(sklearn.svm.SVR()),\n", + " {'estimator__C': [6, 7, 8]},\n", + " scoring=sklearn.metrics.make_scorer(score, greater_is_better=False))\n", + "\n", + "search.fit(x_fit, y_fit)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "3067769c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'mean_fit_time': array([0.07095089, 0.06982546, 0.07134981]),\n", + " 'std_fit_time': array([0.0079291 , 0.00245155, 0.00646164]),\n", + " 'mean_score_time': array([0.01377077, 0.01417546, 0.01358666]),\n", + " 'std_score_time': array([0.00047489, 0.00053198, 0.00094877]),\n", + " 'param_estimator__C': masked_array(data=[6, 7, 8],\n", + " mask=[False, False, False],\n", + " fill_value='?',\n", + " dtype=object),\n", + " 'params': [{'estimator__C': 6}, {'estimator__C': 7}, {'estimator__C': 8}],\n", + " 'split0_test_score': array([-0.85498773, -0.85945549, -0.86226141]),\n", + " 'split1_test_score': array([-1.29125325, -1.27631318, -1.26634836]),\n", + " 'split2_test_score': array([-0.64326025, -0.64460761, -0.64724773]),\n", + " 'split3_test_score': array([-1.20473515, -1.20882402, -1.21358101]),\n", + " 'split4_test_score': array([-2.13002372, -2.13233368, -2.13713761]),\n", + " 'mean_test_score': array([-1.22485202, -1.2243068 , -1.22531523]),\n", + " 'std_test_score': array([0.50974468, 0.50923959, 0.50974731]),\n", + " 'rank_test_score': array([2, 1, 3], dtype=int32)}" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search.cv_results_" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "c0aa92d1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "({'estimator__C': 7}, -1.2243067951746291)" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search.best_params_, search.best_score_" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "796c3941", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.3016646670951195" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "score(search.best_estimator_.predict(x_valid), y_valid)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4fe79bcf", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/0003/notebooks/kaggle-unsupervised-anomaly-detection.ipynb b/examples/0003/notebooks/kaggle-unsupervised-anomaly-detection.ipynb new file mode 100644 index 0000000..4b78088 --- /dev/null +++ b/examples/0003/notebooks/kaggle-unsupervised-anomaly-detection.ipynb @@ -0,0 +1,2846 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "3975a3a8-8d1d-458c-9c18-7deeccbbf205", + "_execution_state": "idle", + "_uuid": "05bef3a5fb05cc62c34cace91131b01c452cff01", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Motivation : \n", + "I read an interesting article about anomaly detection: https://iwringer.wordpress.com/2015/11/17/anomaly-detection-concepts-and-techniques/. \n", + "I wanted to try a few of these techniques to better understand them. I searched an interesting dataset on Kaggle about anomaly detection with simple exemples. I choose one exemple of NAB datasets (thanks for this datasets) and I implemented a few of these algorithms. The goal of this Notebook is just to implement these techniques and understand there main caracteristics. Sometimes, they are not adapted to this datasets. I add some visualizations to understand what the algorithm detect. Hope it can help some people.\n", + "Notebook available (with Markov Chain) here: https://github.com/Vicam/Unsupervised_Anomaly_Detection\n", + "\n", + "# Algorithm implemented :\n", + "- Cluster based anomaly detection (K-mean)\n", + "- Repartition of data into categories then Gaussian/Elliptic Enveloppe on each categories separately\n", + "- Markov Chain\n", + "- Isolation Forest\n", + "- One class SVM\n", + "- RNN (comparison between prediction and reality)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "_cell_guid": "5ac34a72-3992-448a-8ced-12bb329c40a7", + "_execution_state": "idle", + "_uuid": "1fd6aebed8495fb904ca1465245627cef301294d", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# libraries\n", + "#%matplotlib notebook\n", + "\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "\n", + "from matplotlib import pyplot as plt\n", + "\n", + "from sklearn import preprocessing\n", + "from sklearn.decomposition import PCA\n", + "from sklearn.cluster import KMeans\n", + "from sklearn.covariance import EllipticEnvelope\n", + "#from pyemma import msm # not available on Kaggle Kernel\n", + "from sklearn.ensemble import IsolationForest\n", + "from sklearn.svm import OneClassSVM" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "_cell_guid": "828ca1cd-759b-4d8a-a592-eaf2ca691780", + "_execution_state": "idle", + "_uuid": "9eb06b06b146c9aa9533c1a78203dd1025d6e27e", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# some function for later\n", + "\n", + "# # return Series of distance between each point and his distance with the closest centroid\n", + "# def getDistanceByPoint(data, model):\n", + "# distance = pd.Series()\n", + "# for i in range(0,len(data)):\n", + "# Xa = np.array(data.loc[i])\n", + "# Xb = model.cluster_centers_[model.labels_[i]-1]\n", + "# distance.set_value(i, np.linalg.norm(Xa-Xb))\n", + "# return distance\n", + "#\n", + "# # train markov model to get transition matrix\n", + "# def getTransitionMatrix (df):\n", + "# \tdf = np.array(df)\n", + "# \tmodel = msm.estimate_markov_model(df, 1)\n", + "# \treturn model.transition_matrix\n", + "#\n", + "# def markovAnomaly(df, windows_size, threshold):\n", + "# transition_matrix = getTransitionMatrix(df)\n", + "# real_threshold = threshold**windows_size\n", + "# df_anomaly = []\n", + "# for j in range(0, len(df)):\n", + "# if (j < windows_size):\n", + "# df_anomaly.append(0)\n", + "# else:\n", + "# sequence = df[j-windows_size:j]\n", + "# sequence = sequence.reset_index(drop=True)\n", + "# df_anomaly.append(anomalyElement(sequence, real_threshold, transition_matrix))\n", + "# return df_anomaly" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "3da6fa0d-ef0f-42d1-bd59-ee4fb3cfc2ab", + "_execution_state": "idle", + "_uuid": "7459b17ef14b51410807f57f36e887682f2cad33", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# 1 Data\n", + "## 1.1 Extract data\n", + "The dataset is from https://www.kaggle.com/boltzmannbrain/nab \n", + "In realKnownCause/ambient_temperature_system_failure.csv" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "_cell_guid": "bcb68e2d-981d-403d-bfed-655e8a9bdc3c", + "_execution_state": "idle", + "_uuid": "e7d412858498c0da43c102f0ef1db787f1652374", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "df = pd.read_csv(\"dataset/ambient_temperature_system_failure.csv\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "fc3bc10f-54df-44e2-b3a2-cc8b59717c01", + "_execution_state": "idle", + "_uuid": "44c55c1820894f40f650cba31a4723591315345d", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 1.2 Understand data" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "_cell_guid": "6ce4bfe8-1d91-4172-830e-a760e1d63b1e", + "_execution_state": "idle", + "_uuid": "3927fcc620162f8a69100525f83d609cd7b589f4", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 7267 entries, 0 to 7266\n", + "Data columns (total 2 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 timestamp 7267 non-null object \n", + " 1 value 7267 non-null float64\n", + "dtypes: float64(1), object(1)\n", + "memory usage: 113.7+ KB\n", + "None\n" + ] + } + ], + "source": [ + "print(df.info())" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "_cell_guid": "d5b77b21-3793-42ab-b291-11189be9071f", + "_execution_state": "idle", + "_uuid": "5ef1df3d1671939759548a4199fd05c17ec251e1", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 2013-07-04 00:00:00\n", + "1 2013-07-04 01:00:00\n", + "2 2013-07-04 02:00:00\n", + "3 2013-07-04 03:00:00\n", + "4 2013-07-04 04:00:00\n", + "5 2013-07-04 05:00:00\n", + "6 2013-07-04 06:00:00\n", + "7 2013-07-04 07:00:00\n", + "8 2013-07-04 08:00:00\n", + "9 2013-07-04 09:00:00\n", + "Name: timestamp, dtype: object\n" + ] + } + ], + "source": [ + "# check the timestamp format and frequence \n", + "print(df['timestamp'].head(10))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "_cell_guid": "5b0e5bea-f11b-4f81-93fd-a817a6f9f579", + "_execution_state": "idle", + "_uuid": "899ad93b122b2144c304ff31f93568e183c32c6c", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "71.24243270828815\n" + ] + } + ], + "source": [ + "# check the temperature mean\n", + "print(df['value'].mean())" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "_cell_guid": "0395bd90-61f4-49db-b157-b58853afe2f8", + "_execution_state": "idle", + "_uuid": "d5e63d3da28366ddf2fbaa2653d75536332713dc", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# change the type of timestamp column for plotting\n", + "df['timestamp'] = pd.to_datetime(df['timestamp'])\n", + "# change fahrenheit to °C (temperature mean= 71 -> fahrenheit)\n", + "df['value'] = (df['value'] - 32) * 5/9\n", + "# plot the data\n", + "df.plot(x='timestamp', y='value')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "90075ebb-0f2e-4258-8901-03a25ce67172", + "_execution_state": "idle", + "_uuid": "c62e4d5f8bf8a855aab7bfa86273d731a5638b6d", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 1.3 Feature engineering\n", + "Extracting some useful features" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
timestampvaluehoursdaylightDayOfTheWeekWeekDaytime_epochcategoriesclusterprincipal_feature1principal_feature2anomaly21anomaly22anomaly25
02013-07-04 00:00:0021.044908003113728960220.0661911.755363000
12013-07-04 01:00:0021.78901510311372899622-0.0094131.647609000
22013-07-04 02:00:0021.59878120311372903222-0.0093591.577677000
32013-07-04 03:00:0020.533000303113729068220.0615921.543184000
42013-07-04 04:00:0020.713084403113729104220.0316601.458262000
.............................................
72622014-05-28 11:00:0022.427892111211401274831-0.663130-0.218870000
72632014-05-28 12:00:0022.318309121211401278436-0.669607-0.292067000
72642014-05-28 13:00:0022.248092131211401282036-0.679271-0.366858000
72652014-05-28 14:00:0022.125126141211401285636-0.684664-0.439514000
72662014-05-28 15:00:0022.546716151211401289236-0.734152-0.534212000
\n", + "

7267 rows × 14 columns

\n", + "
" + ], + "text/plain": [ + " timestamp value hours daylight DayOfTheWeek WeekDay \\\n", + "0 2013-07-04 00:00:00 21.044908 0 0 3 1 \n", + "1 2013-07-04 01:00:00 21.789015 1 0 3 1 \n", + "2 2013-07-04 02:00:00 21.598781 2 0 3 1 \n", + "3 2013-07-04 03:00:00 20.533000 3 0 3 1 \n", + "4 2013-07-04 04:00:00 20.713084 4 0 3 1 \n", + "... ... ... ... ... ... ... \n", + "7262 2014-05-28 11:00:00 22.427892 11 1 2 1 \n", + "7263 2014-05-28 12:00:00 22.318309 12 1 2 1 \n", + "7264 2014-05-28 13:00:00 22.248092 13 1 2 1 \n", + "7265 2014-05-28 14:00:00 22.125126 14 1 2 1 \n", + "7266 2014-05-28 15:00:00 22.546716 15 1 2 1 \n", + "\n", + " time_epoch categories cluster principal_feature1 principal_feature2 \\\n", + "0 13728960 2 2 0.066191 1.755363 \n", + "1 13728996 2 2 -0.009413 1.647609 \n", + "2 13729032 2 2 -0.009359 1.577677 \n", + "3 13729068 2 2 0.061592 1.543184 \n", + "4 13729104 2 2 0.031660 1.458262 \n", + "... ... ... ... ... ... \n", + "7262 14012748 3 1 -0.663130 -0.218870 \n", + "7263 14012784 3 6 -0.669607 -0.292067 \n", + "7264 14012820 3 6 -0.679271 -0.366858 \n", + "7265 14012856 3 6 -0.684664 -0.439514 \n", + "7266 14012892 3 6 -0.734152 -0.534212 \n", + "\n", + " anomaly21 anomaly22 anomaly25 \n", + "0 0 0 0 \n", + "1 0 0 0 \n", + "2 0 0 0 \n", + "3 0 0 0 \n", + "4 0 0 0 \n", + "... ... ... ... \n", + "7262 0 0 0 \n", + "7263 0 0 0 \n", + "7264 0 0 0 \n", + "7265 0 0 0 \n", + "7266 0 0 0 \n", + "\n", + "[7267 rows x 14 columns]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "new_t = []\n", + "from datetime import datetime\n", + "my_list = df.copy().values.tolist()\n", + "for i in range(len(my_list)):\n", + " my_list[i][2] = my_list[i][0].hour,\n", + " my_list[i][3] = int((my_list[i][0].hour >= 7) & (my_list[i][0].hour <= 22)),\n", + " my_list[i][4] = my_list[i][0].weekday(),\n", + " my_list[i][5] =int(my_list[i][0].weekday() < 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "my_list[0][2][0]" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_280401/3799850664.py:1: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.\n", + " np.array(my_list)\n" + ] + }, + { + "data": { + "text/plain": [ + "array([[Timestamp('2013-07-04 00:00:00'), 21.04490841111111, (0,), ...,\n", + " 0, 0, 0],\n", + " [Timestamp('2013-07-04 01:00:00'), 21.789015033333335, (1,), ...,\n", + " 0, 0, 0],\n", + " [Timestamp('2013-07-04 02:00:00'), 21.598780533333336, (2,), ...,\n", + " 0, 0, 0],\n", + " ...,\n", + " [Timestamp('2014-05-28 13:00:00'), 22.248091916666667, (13,), ...,\n", + " 0, 0, 0],\n", + " [Timestamp('2014-05-28 14:00:00'), 22.12512582222222, (14,), ...,\n", + " 0, 0, 0],\n", + " [Timestamp('2014-05-28 15:00:00'), 22.54671587777778, (15,), ...,\n", + " 0, 0, 0]], dtype=object)" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.array(my_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "_cell_guid": "e1d36c51-0d96-43c5-8a6c-9f26d7997788", + "_execution_state": "idle", + "_uuid": "a86baa1be987a4de8b4b192af475ab4fde077d82", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# the hours and if it's night or day (7:00-22:00)\n", + "df['hours'] = df['timestamp'].dt.hour\n", + "df['daylight'] = ((df['hours'] >= 7) & (df['hours'] <= 22)).astype(int)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0 0\n", + "1 1\n", + "2 2\n", + "3 3\n", + "4 4\n", + " ..\n", + "7262 11\n", + "7263 12\n", + "7264 13\n", + "7265 14\n", + "7266 15\n", + "Name: hours, Length: 7267, dtype: int64" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "_cell_guid": "b5dc68e4-89b0-4248-853d-e00e5e9ef4e9", + "_execution_state": "idle", + "_uuid": "c3d4a71542f5080b5f87a27cf681f542a665a34c", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# the day of the week (Monday=0, Sunday=6) and if it's a week end day or week day.\n", + "df['DayOfTheWeek'] = df['timestamp'].dt.dayofweek\n", + "df['WeekDay'] = (df['DayOfTheWeek'] < 5).astype(int)\n", + "# An estimation of anomly population of the dataset (necessary for several algorithm)\n", + "outliers_fraction = 0.01" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuehoursdaylightDayOfTheWeekWeekDay
021.0449080031
121.7890151031
221.5987812031
320.5330003031
420.7130844031
..................
726222.42789211121
726322.31830912121
726422.24809213121
726522.12512614121
726622.54671615121
\n", + "

7267 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " value hours daylight DayOfTheWeek WeekDay\n", + "0 21.044908 0 0 3 1\n", + "1 21.789015 1 0 3 1\n", + "2 21.598781 2 0 3 1\n", + "3 20.533000 3 0 3 1\n", + "4 20.713084 4 0 3 1\n", + "... ... ... ... ... ...\n", + "7262 22.427892 11 1 2 1\n", + "7263 22.318309 12 1 2 1\n", + "7264 22.248092 13 1 2 1\n", + "7265 22.125126 14 1 2 1\n", + "7266 22.546716 15 1 2 1\n", + "\n", + "[7267 rows x 5 columns]" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[['value', 'hours', 'daylight', 'DayOfTheWeek', 'WeekDay']]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "_cell_guid": "14cd3d26-a411-47f8-9238-2794284e8c9b", + "_execution_state": "idle", + "_uuid": "189cd0890e09373ed847b258d1354bd6eae7bfd8", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# time with int to plot easily\n", + "df['time_epoch'] = (df['timestamp'].astype(np.int64)/100000000000).astype(np.int64)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "_cell_guid": "0c5f5102-2122-41da-980d-c58c8438f190", + "_execution_state": "idle", + "_uuid": "60e1a0334d22e2c0c96ac279cc48c66f99b43248", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# creation of 4 distinct categories that seem useful (week end/day week & night/day)\n", + "df['categories'] = df['WeekDay']*2 + df['daylight']\n", + "\n", + "a = df.loc[df['categories'] == 0, 'value']\n", + "b = df.loc[df['categories'] == 1, 'value']\n", + "c = df.loc[df['categories'] == 2, 'value']\n", + "d = df.loc[df['categories'] == 3, 'value']\n", + "\n", + "fig, ax = plt.subplots()\n", + "a_heights, a_bins = np.histogram(a)\n", + "b_heights, b_bins = np.histogram(b, bins=a_bins)\n", + "c_heights, c_bins = np.histogram(c, bins=a_bins)\n", + "d_heights, d_bins = np.histogram(d, bins=a_bins)\n", + "\n", + "width = (a_bins[1] - a_bins[0])/6\n", + "\n", + "ax.bar(a_bins[:-1], a_heights*100/a.count(), width=width, facecolor='blue', label='WeekEndNight')\n", + "ax.bar(b_bins[:-1]+width, (b_heights*100/b.count()), width=width, facecolor='green', label ='WeekEndLight')\n", + "ax.bar(c_bins[:-1]+width*2, (c_heights*100/c.count()), width=width, facecolor='red', label ='WeekDayNight')\n", + "ax.bar(d_bins[:-1]+width*3, (d_heights*100/d.count()), width=width, facecolor='black', label ='WeekDayLight')\n", + "\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "57fc7caa-fb96-491a-9857-3c6e58e8cade", + "_execution_state": "idle", + "_uuid": "3d980e3fcede78e8b9a3719cf3ad1da9ece24bff", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "We can see that the temperature is more stable during daylight of business day.\n", + "# 2 Models\n", + "## 2.1 Cluster only\n", + "#### Use for collective anomalies (unordered). \n", + "\n", + "We group together the usual combination of features. The points that are far from the cluster are points with usual combination of features.We consider those points as anomalies." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "_cell_guid": "01224c89-4372-4f8b-bfb8-d13eb4357264", + "_execution_state": "idle", + "_uuid": "7dfbf8b613b235120c39e11a8a87f3c5e90022e3", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Take useful feature and standardize them\n", + "data = df[['value', 'hours', 'daylight', 'DayOfTheWeek', 'WeekDay']]\n", + "min_max_scaler = preprocessing.StandardScaler()\n", + "np_scaled = min_max_scaler.fit_transform(data)\n", + "data = pd.DataFrame(np_scaled)\n", + "# reduce to 2 importants features\n", + "pca = PCA(n_components=2)\n", + "data = pca.fit_transform(data)\n", + "# standardize these 2 new features\n", + "min_max_scaler = preprocessing.StandardScaler()\n", + "np_scaled = min_max_scaler.fit_transform(data)\n", + "data = pd.DataFrame(np_scaled)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "_cell_guid": "be39cb8b-bcc7-4288-95cb-8c95291a55ba", + "_execution_state": "idle", + "_uuid": "452855f2761685d48a0bf360f253ecd18fe379fa", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# calculate with different number of centroids to see the loss plot (elbow method)\n", + "n_cluster = range(1, 20)\n", + "kmeans = [KMeans(n_clusters=i).fit(data) for i in n_cluster]\n", + "scores = [kmeans[i].score(data) for i in range(len(kmeans))]\n", + "fig, ax = plt.subplots()\n", + "ax.plot(n_cluster, scores)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "_cell_guid": "69beec11-d424-4646-b964-d5fbbf8a6550", + "_execution_state": "idle", + "_uuid": "0736ab2c718cbc4138b07deef6c20a690dcecdc7", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3 731\n", + "6 659\n", + "4 621\n", + "9 612\n", + "1 601\n", + "7 599\n", + "2 576\n", + "11 497\n", + "12 438\n", + "5 358\n", + "0 354\n", + "8 347\n", + "10 332\n", + "13 304\n", + "14 238\n", + "Name: cluster, dtype: int64" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Not clear for me, I choose 15 centroids arbitrarily and add these data to the central dataframe\n", + "df['cluster'] = kmeans[14].predict(data)\n", + "df['principal_feature1'] = data[0]\n", + "df['principal_feature2'] = data[1]\n", + "df['cluster'].value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "_cell_guid": "d14ac727-eac4-4ae1-99c8-b9fb9b1301e7", + "_execution_state": "idle", + "_uuid": "64ac4322fbf2bc36539e0a60ff8e45254dc63afe", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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7+iZ3hkhBE6nrddjw+HzmUrIFb2iwi8KRZwILtMZbvm6g90aFYpYokRdoYgjquGVYrAZUPjl1ltv5c0338RmszFhwgR+85vfkJWVFSvbFIqUx+PxY7MZIWLoUlVwKuLCgDz1Y489lueff57nnnuOSZMmcffdd8fKLoViSODzmXg8wYrPgwtKBFcKUijiwYBE/bjjjsNo6/U5b948ysvLY2KUQjGUaGnxUVvbQkuLl6YmDzU1zSoNURE3hBxop/42rr76ak477TTOOuusPve1LAvTHJxYo653XRsyWUhGu5LRJlB2RYuyKzpSya5IJtf7jKl//etfp7q6usfr119/PSeffDIAd955J7quc+aZZ0ZkrGkOXsVbTk5aUlbXJaNdyWgTKLuiRdkVHalk18iRmX0e16eo33fffb1uf/LJJ1m7di333XffoK2OrlAoFIrQDCj75e233+bee+/lwQcfxOVyxcomhUKhUPSTAYn6r3/9a3w+H6tXrwZg7ty53HTTTTExTKFQKBTRMyBRf+2112Jlh0KhUChiQMyyXxQKhUKReFSbAIVCoRhCKFFXKBSKIYQSdYVCoRhCKFFXKBSKIYQSdYVCoRhCKFFXKBSKIYQSdYVCoRhCDDlRf+mll1ixYgWHHHIIn3/+edj9lixZwsqVKznrrLNYtWpV0tj19ttvc8opp7Bs2TL+/ve/x9Wm+vp6Vq9ezfLly1m9ejUNDQ0h95s5cyZnnXUWZ511FldffXXc7Onrvft8Pq6//nqWLVvG+eefT2lpadxsicauJ598kqOOOqrjM3rsscfibtOPfvQjjj76aM4444yQ26WU/N///R/Lli1j5cqVfPHFF3G3KRK71q1bx4IFCzo+q7/85S+DYldZWRmXXHIJp59+OitWrOD+++/vsc9gf2aR2NSvz0sOMXbu3Cl37dolL774Yrlp06aw+5100kmypqYmqewKBAJy6dKlct++fdLr9cqVK1fKHTt2xM2mW2+9Vd59991SSinvvvtu+bvf/S7kfvPmzYubDe1E8t4ffPBB+bOf/UxKKeXzzz8vv/vd7yaFXU888YT81a9+FXdbOrN+/Xq5efNmuWLFipDb165dKy+//HJpWZb89NNP5XnnnZcUdn344YfyqquuGhRbOlNRUSE3b94spZSyqalJLl++vMf3ONifWSQ29efzGnKe+pQpUygqKkq0GT2IxK5NmzYxceJExo8fj91uZ8WKFbzxxhtxs+mNN97g7LPPBuDss8/m9ddfj9tYfRHJe1+zZg3nnHMOAKeccgoffPABMs4F0YP9nUTKwoULyc7ODru9/bsVQjBv3jwaGxuprKxMuF2JoqCggFmzZgGQkZFBUVERFRUVXfYZ7M8sEpv6w5AT9Wi4/PLLWbVqFY888kiiTQGgoqKCUaNGdfy7sLAwJl9yOGpqaigoKABg5MiR1NTUhNzP6/WyatUqLrjggrgJfyTvvaKigtGjRwNgGAaZmZnU1dXFxZ5o7AJ49dVXWblyJddddx1lZWVxtSkSuts9atSouF5L0bBx40bOPPNMrrjiCnbs2DHo45eWlrJ161bmzp3b5fVEfmbhbILoP68BNfRKFJEs3NEX//3vfyksLKSmpobVq1dTVFTEwoULE25XrOnNps4IIcL2w3/zzTcpLCykpKSEyy67jOnTpzNhwoR4mJuSnHTSSZxxxhnY7XYefvhhfvjDH/Lvf/870WYlJbNmzWLNmjWkp6fz1ltv8e1vf5tXX3110MZvaWnhuuuu48c//jEZGRmDNm5v9GZTfz6vlBT1vhbuiITCwkIA8vLyWLZsGZs2bRqwqA/UrsLCwi7rvFZUVHTYGQ+b8vLyqKyspKCggMrKSkaMGBHWLoDx48dz5JFHsmXLlpiLeiTvvbCwkLKyMkaNGkUgEKCpqYnc3NyY2tEfuzrbcP755/P73/8+rjZFQne7y8vLB3wtxYLOonXCCSfwq1/9itra2rDXXizx+/1cd911rFy5kuXLl/fYnojPrC+b+vN5DcvwS2trK83NzR3//9577zFt2rQEWwVz5syhuLiYkpISfD4fL7zwAkuWLInbeEuWLOHpp58G4Omnn2bp0qU99mloaMDn8wFQW1vLhg0bmDp1asxtieS9L1myhKeeegqAV155haOOOiruq21FYlfnuOuaNWuYMmVKXG2KhPbvVkrJxo0byczM7Ai1JZKqqqqOeZBNmzZhWVbcb8wQzGz5yU9+QlFRUcf6D90Z7M8sEpv683kNuda7r732Gr/+9a+pra0lKyuLmTNn8o9//IOKigp++tOfcs8991BSUsK3v/1tAEzT5IwzzuCaa65JuF0Ab731FrfccgumaXLuuefG1a66ujquv/56ysrKGDNmDLfffjs5OTl8/vnnPPzww9x8881s2LCBX/ziFwghkFJy6aWXcv7558fFnlDv/Y477mD27NksXboUr9fL97//fbZu3Up2djZ//OMfGT9+fFxsicauP/zhD6xZswZd18nOzuaXv/xl3IX9hhtuYP369dTV1ZGXl8e1115LIBAA4MILL0RKyU033cQ777yDy+XilltuYc6cOXG1KRK7HnzwQf773/+i6zpOp5Mbb7yRww8/PO52ffzxx1x00UVMnz4dTdM6bD1w4ECHbYP9mUViU38+ryEn6gqFQjGcGZbhF4VCoRiqKFFXKBSKIYQSdYVCoRhCKFFXKBSKIYQSdYVCoRhCKFFXKBSKIYQSdYVCoRhC/H9UvXaDae3ELwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#plot the different clusters with the 2 main features\n", + "fig, ax = plt.subplots()\n", + "colors = {0:'red', 1:'blue', 2:'green', 3:'pink', 4:'black', 5:'orange', 6:'cyan', 7:'yellow', 8:'brown', 9:'purple', 10:'white', 11: 'grey', 12:'lightblue', 13:'lightgreen', 14: 'darkgrey'}\n", + "ax.scatter(df['principal_feature1'], df['principal_feature2'], c=df[\"cluster\"].apply(lambda x: colors[x]))\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# return Series of distance between each point and his distance with the closest centroid\n", + "def getDistanceByPoint(data, model):\n", + " distance = pd.Series()\n", + " for i in range(0,len(data)):\n", + " Xa = np.array(data.loc[i])\n", + " Xb = model.cluster_centers_[model.labels_[i]-1]\n", + " distance.at[i] = np.linalg.norm(Xa-Xb)\n", + " return distance" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "_cell_guid": "cd260641-7e32-4305-b7a8-008fcff4dcfc", + "_execution_state": "idle", + "_uuid": "1a84160cfb1d419c3a0a57530c079bfa6b1a11b1", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_280401/4109955861.py:3: FutureWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.\n", + " distance = pd.Series()\n" + ] + } + ], + "source": [ + "# get the distance between each point and its nearest centroid. The biggest distances are considered as anomaly\n", + "distance = getDistanceByPoint(data, kmeans[14])\n", + "number_of_outliers = int(outliers_fraction*len(distance))\n", + "threshold = distance.nlargest(number_of_outliers).min()\n", + "# anomaly21 contain the anomaly result of method 2.1 Cluster (0:normal, 1:anomaly) \n", + "df['anomaly21'] = (distance >= threshold).astype(int)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "_cell_guid": "38a6fd23-954f-400c-8065-f4bea22183d5", + "_execution_state": "idle", + "_uuid": "40e05059a9a66b52fba53c654232a356df3a8828", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly with cluster view\n", + "fig, ax = plt.subplots()\n", + "colors = {0:'blue', 1:'red'}\n", + "ax.scatter(df['principal_feature1'], df['principal_feature2'], c=df[\"anomaly21\"].apply(lambda x: colors[x]))\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "_cell_guid": "56a2455c-ee60-491e-8010-aab56eeb8e3f", + "_execution_state": "idle", + "_uuid": "8ae64bd8c8b942e2b8aaf9e6b75d40df7b5c4c6c", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly throughout time (viz 1)\n", + "fig, ax = plt.subplots()\n", + "\n", + "a = df.loc[df['anomaly21'] == 1, ['time_epoch', 'value']] #anomaly\n", + "\n", + "ax.plot(df['time_epoch'], df['value'], color='blue')\n", + "ax.scatter(a['time_epoch'],a['value'], color='red')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "_cell_guid": "9926f2fb-6fa2-44e8-8b97-c5e4b6de311f", + "_execution_state": "idle", + "_uuid": "3b12e1fe4c788500c1734de1c676dd4f429afe7c", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly with temperature repartition (viz 2)\n", + "a = df.loc[df['anomaly21'] == 0, 'value']\n", + "b = df.loc[df['anomaly21'] == 1, 'value']\n", + "\n", + "fig, axs = plt.subplots()\n", + "axs.hist([a,b], bins=32, stacked=True, color=['blue', 'red'], label=['normal', 'anomaly'])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "211bffc8-1a4e-4e47-87af-2c50dd896d02", + "_execution_state": "idle", + "_uuid": "c7840c929e610cd29d1775e30ad45364407dc7a0", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Cluster method detects the low temperature around the end of record as unusually low. It doesn't detect the highest temperature pic.\n", + "## 2.2 Categories + Gaussian\n", + "#### Use for contextual data and collective anomalies (unordered). \n", + "We will separate data by (what we think of) important categories. Or we can separate data based on different cluster (method 2.3). Then we find outliers (gaussian repartition, unimodal) by categories independently. " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "_cell_guid": "46755793-473d-413d-a0fc-b08b7d589676", + "_execution_state": "idle", + "_uuid": "0c35172a17989a3cf536fca25a4560a933eaa374", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# creation of 4 differents data set based on categories defined before\n", + "df_class0 = df.loc[df['categories'] == 0, 'value']\n", + "df_class1 = df.loc[df['categories'] == 1, 'value']\n", + "df_class2 = df.loc[df['categories'] == 2, 'value']\n", + "df_class3 = df.loc[df['categories'] == 3, 'value']" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "_cell_guid": "1b973d0e-300a-4875-834d-d6011b43d25f", + "_execution_state": "idle", + "_uuid": "8562a05945620307d0a63ccd36da46e5c4434a3c", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# plot the temperature repartition by categories\n", + "fig, axs = plt.subplots(2,2)\n", + "df_class0.hist(ax=axs[0,0],bins=32)\n", + "df_class1.hist(ax=axs[0,1],bins=32)\n", + "df_class2.hist(ax=axs[1,0],bins=32)\n", + "df_class3.hist(ax=axs[1,1],bins=32)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "_cell_guid": "f269fc3e-92da-470a-89ba-83cc51c07272", + "_execution_state": "idle", + "_uuid": "bbedf1d3f83cf7ae6b4ff65f979479a44b03bfe0", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# apply ellipticEnvelope(gaussian distribution) at each categories\n", + "envelope = EllipticEnvelope(contamination = outliers_fraction) \n", + "X_train = df_class0.values.reshape(-1,1)\n", + "envelope.fit(X_train)\n", + "df_class0 = pd.DataFrame(df_class0)\n", + "df_class0['deviation'] = envelope.decision_function(X_train)\n", + "df_class0['anomaly'] = envelope.predict(X_train)\n", + "\n", + "envelope = EllipticEnvelope(contamination = outliers_fraction) \n", + "X_train = df_class1.values.reshape(-1,1)\n", + "envelope.fit(X_train)\n", + "df_class1 = pd.DataFrame(df_class1)\n", + "df_class1['deviation'] = envelope.decision_function(X_train)\n", + "df_class1['anomaly'] = envelope.predict(X_train)\n", + "\n", + "envelope = EllipticEnvelope(contamination = outliers_fraction) \n", + "X_train = df_class2.values.reshape(-1,1)\n", + "envelope.fit(X_train)\n", + "df_class2 = pd.DataFrame(df_class2)\n", + "df_class2['deviation'] = envelope.decision_function(X_train)\n", + "df_class2['anomaly'] = envelope.predict(X_train)\n", + "\n", + "envelope = EllipticEnvelope(contamination = outliers_fraction) \n", + "X_train = df_class3.values.reshape(-1,1)\n", + "envelope.fit(X_train)\n", + "df_class3 = pd.DataFrame(df_class3)\n", + "df_class3['deviation'] = envelope.decision_function(X_train)\n", + "df_class3['anomaly'] = envelope.predict(X_train)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "_cell_guid": "b309d1b4-3bfe-4803-85c0-f03b71bb00fc", + "_execution_state": "idle", + "_uuid": "f622f36a8c6d0d1abfde310cf25a0ff5ddba1c78", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# plot the temperature repartition by categories with anomalies\n", + "a0 = df_class0.loc[df_class0['anomaly'] == 1, 'value']\n", + "b0 = df_class0.loc[df_class0['anomaly'] == -1, 'value']\n", + "\n", + "a1 = df_class1.loc[df_class1['anomaly'] == 1, 'value']\n", + "b1 = df_class1.loc[df_class1['anomaly'] == -1, 'value']\n", + "\n", + "a2 = df_class2.loc[df_class2['anomaly'] == 1, 'value']\n", + "b2 = df_class2.loc[df_class2['anomaly'] == -1, 'value']\n", + "\n", + "a3 = df_class3.loc[df_class3['anomaly'] == 1, 'value']\n", + "b3 = df_class3.loc[df_class3['anomaly'] == -1, 'value']\n", + "\n", + "fig, axs = plt.subplots(2,2)\n", + "axs[0,0].hist([a0,b0], bins=32, stacked=True, color=['blue', 'red'], label=['normal', 'anomaly'])\n", + "axs[0,1].hist([a1,b1], bins=32, stacked=True, color=['blue', 'red'], label=['normal', 'anomaly'])\n", + "axs[1,0].hist([a2,b2], bins=32, stacked=True, color=['blue', 'red'], label=['normal', 'anomaly'])\n", + "axs[1,1].hist([a3,b3], bins=32, stacked=True, color=['blue', 'red'], label=['normal', 'anomaly'])\n", + "axs[0,0].set_title(\"WeekEndNight\")\n", + "axs[0,1].set_title(\"WeekEndLight\")\n", + "axs[1,0].set_title(\"WeekDayNight\")\n", + "axs[1,1].set_title(\"WeekDayLight\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "_cell_guid": "771633b5-7924-4f98-9a9e-66dff0d8fb1e", + "_execution_state": "idle", + "_uuid": "250cab346e794bbdf6b3860e8880bb2c13d5a270", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# add the data to the main \n", + "df_class = pd.concat([df_class0, df_class1, df_class2, df_class3])\n", + "df['anomaly22'] = df_class['anomaly']\n", + "df['anomaly22'] = np.array(df['anomaly22'] == -1).astype(int) " + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "_cell_guid": "ea6efd5d-014d-4257-9522-5f2560f9a7df", + "_execution_state": "idle", + "_uuid": "533cd11f31bc2ad2b2381b38a3ee722566ddb195", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly throughout time (viz 1)\n", + "fig, ax = plt.subplots()\n", + "\n", + "a = df.loc[df['anomaly22'] == 1, ('time_epoch', 'value')] #anomaly\n", + "\n", + "ax.plot(df['time_epoch'], df['value'], color='blue')\n", + "ax.scatter(a['time_epoch'],a['value'], color='red')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "_cell_guid": "9bdd7add-99b4-42c6-9d3f-3670b9f9ebcf", + "_execution_state": "idle", + "_uuid": "0642b30ee151c600f5d9141a1b36b69cbd328a2c", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly with temperature repartition (viz 2)\n", + "a = df.loc[df['anomaly22'] == 0, 'value']\n", + "b = df.loc[df['anomaly22'] == 1, 'value']\n", + "\n", + "fig, axs = plt.subplots()\n", + "axs.hist([a,b], bins=32, stacked=True, color=['blue', 'red'], label=['normal', 'anomaly'])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "55a0778e-3606-4ce1-847c-06c148c55fb5", + "_execution_state": "idle", + "_uuid": "033a39b8f36b5089417199aeb21e37242cff0403", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Good detections of extreme values and context separation add some precision to the detection.\n", + "## 2.3 Cluster+Gaussian\n", + "Similar to 2.2 solution but with cluster to separate data in different group." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "e966a2b5-b49d-489b-bb8f-88eff97deb6d", + "_execution_state": "idle", + "_uuid": "f796a11f0beacf7b60b9c02118f4517a0597cbff", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 2.4 Markov chains\n", + "#### Use for sequential anomalies (ordered)\n", + "We need discretize the data points in define states for markov chain. We will just take 'value' to define state for this example and define 5 levels of value (very low, low, average, high, very high)/(VL, L, A, H, VH).\n", + "Markov chain will calculate the probability of sequence like (VL, L, L, A, A, L, A). If the probability is very weak we consider the sequence as an anomaly." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "_cell_guid": "02d2db70-197e-4fc2-b027-b20f8565c463", + "_execution_state": "idle", + "_uuid": "f54f23f37f06672cb19d89765a6cbfadcbe32369", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# definition of the different state\n", + "x1 = (df['value'] <=18).astype(int)\n", + "x2= ((df['value'] > 18) & (df['value']<=21)).astype(int)\n", + "x3 = ((df['value'] > 21) & (df['value']<=24)).astype(int)\n", + "x4 = ((df['value'] > 24) & (df['value']<=27)).astype(int)\n", + "x5 = (df['value'] >27).astype(int)\n", + "df_mm = x1 + 2*x2 + 3*x3 + 4*x4 + 5*x5\n", + "\n", + "# getting the anomaly labels for our dataset (evaluating sequence of 5 values and anomaly = less than 20% probable)\n", + "# I USE pyemma NOT AVAILABLE IN KAGGLE KERNEL\n", + "#df_anomaly = markovAnomaly(df_mm, 5, 0.20)\n", + "#df_anomaly = pd.Series(df_anomaly)\n", + "#print(df_anomaly.value_counts())" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "_cell_guid": "20f432e1-f393-4bc3-8d2d-32e4397e58a1", + "_execution_state": "idle", + "_uuid": "972ce803b0a56160d465e6c1f6ff0ddb9d564d85", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\n# add the data to the main \\ndf['anomaly24'] = df_anomaly\\n\\n# visualisation of anomaly throughout time (viz 1)\\nfig, ax = plt.subplots()\\n\\na = df.loc[df['anomaly24'] == 1, ('time_epoch', 'value')] #anomaly\\n\\nax.plot(df['time_epoch'], df['value'], color='blue')\\nax.scatter(a['time_epoch'],a['value'], color='red')\\nplt.show()\\n\"" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"\n", + "# add the data to the main \n", + "df['anomaly24'] = df_anomaly\n", + "\n", + "# visualisation of anomaly throughout time (viz 1)\n", + "fig, ax = plt.subplots()\n", + "\n", + "a = df.loc[df['anomaly24'] == 1, ('time_epoch', 'value')] #anomaly\n", + "\n", + "ax.plot(df['time_epoch'], df['value'], color='blue')\n", + "ax.scatter(a['time_epoch'],a['value'], color='red')\n", + "plt.show()\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "_cell_guid": "c92f059a-7938-43f7-8d2d-426f772bae84", + "_execution_state": "idle", + "_uuid": "a73dbcfb3d0cddbde36fbd1dbc5a2a45e69f1262", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\n# visualisation of anomaly with temperature repartition (viz 2)\\na = df.loc[df['anomaly24'] == 0, 'value']\\nb = df.loc[df['anomaly24'] == 1, 'value']\\n\\nfig, axs = plt.subplots()\\naxs.hist([a,b], bins=32, stacked=True, color=['blue', 'red'])\\nplt.legend()\\nplt.show()\\n\"" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"\n", + "# visualisation of anomaly with temperature repartition (viz 2)\n", + "a = df.loc[df['anomaly24'] == 0, 'value']\n", + "b = df.loc[df['anomaly24'] == 1, 'value']\n", + "\n", + "fig, axs = plt.subplots()\n", + "axs.hist([a,b], bins=32, stacked=True, color=['blue', 'red'])\n", + "plt.legend()\n", + "plt.show()\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "796a1096-dd90-4bfd-ba52-1fc3c0b99a16", + "_execution_state": "idle", + "_uuid": "2e6accac972f6adf0d907f458943ef5f2129e7df", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Detect unusual sequence but not extreme value. More difficult to evaluate the relevance on this example. The sequence size (5) should be match with some interesting cycle.\n", + "## 2.5 Isolation Forest\n", + "#### Use for collective anomalies (unordered).\n", + "Simple, works well with different data repartition and efficient with high dimention data." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuehoursdaylightDayOfTheWeekWeekDay
021.0449080031
121.7890151031
221.5987812031
320.5330003031
420.7130844031
..................
726222.42789211121
726322.31830912121
726422.24809213121
726522.12512614121
726622.54671615121
\n", + "

7267 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " value hours daylight DayOfTheWeek WeekDay\n", + "0 21.044908 0 0 3 1\n", + "1 21.789015 1 0 3 1\n", + "2 21.598781 2 0 3 1\n", + "3 20.533000 3 0 3 1\n", + "4 20.713084 4 0 3 1\n", + "... ... ... ... ... ...\n", + "7262 22.427892 11 1 2 1\n", + "7263 22.318309 12 1 2 1\n", + "7264 22.248092 13 1 2 1\n", + "7265 22.125126 14 1 2 1\n", + "7266 22.546716 15 1 2 1\n", + "\n", + "[7267 rows x 5 columns]" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "_cell_guid": "7b550505-0752-43a0-b21d-6dc0fd8105e2", + "_execution_state": "idle", + "_uuid": "0a5641fd4d56e1985f923a92f40138676a4fe54f", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 7195\n", + "1 72\n", + "Name: anomaly25, dtype: int64\n" + ] + } + ], + "source": [ + "# Take useful feature and standardize them \n", + "data = df[['value', 'hours', 'daylight', 'DayOfTheWeek', 'WeekDay']]\n", + "\n", + "min_max_scaler = preprocessing.StandardScaler()\n", + "np_scaled = min_max_scaler.fit_transform(data)\n", + "data = pd.DataFrame(np_scaled)\n", + "\n", + "# train isolation forest \n", + "model = IsolationForest(contamination = outliers_fraction)\n", + "model.fit(data)\n", + "# add the data to the main \n", + "df['anomaly25'] = pd.Series(model.predict(data))\n", + "df['anomaly25'] = df['anomaly25'].map( {1: 0, -1: 1} )\n", + "\n", + "print(df['anomaly25'].value_counts())\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0 0\n", + "1 0\n", + "2 0\n", + "3 0\n", + "4 0\n", + " ..\n", + "7262 0\n", + "7263 0\n", + "7264 0\n", + "7265 0\n", + "7266 0\n", + "Name: anomaly25, Length: 7267, dtype: int64" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df['anomaly25']" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "_cell_guid": "34e8c98b-a99d-44f7-8aad-a599371b3703", + "_execution_state": "idle", + "_uuid": "f5ac7ddd6a6ac149c6e88664abc9fb5401756261", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly throughout time (viz 1)\n", + "fig, ax = plt.subplots()\n", + "\n", + "a = df.loc[df['anomaly25'] == 1, ['time_epoch', 'value']] #anomaly\n", + "\n", + "ax.plot(df['time_epoch'], df['value'], color='blue')\n", + "ax.scatter(a['time_epoch'],a['value'], color='red')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
time_epochvalue
951373238017.914796
7351375657220.094145
9031376262017.980327
10711376866818.258468
12391377471617.994009
.........
70101400367616.633972
70341400454014.533523
70351400457614.679799
71781400972416.980344
72021401058816.373580
\n", + "

72 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " time_epoch value\n", + "95 13732380 17.914796\n", + "735 13756572 20.094145\n", + "903 13762620 17.980327\n", + "1071 13768668 18.258468\n", + "1239 13774716 17.994009\n", + "... ... ...\n", + "7010 14003676 16.633972\n", + "7034 14004540 14.533523\n", + "7035 14004576 14.679799\n", + "7178 14009724 16.980344\n", + "7202 14010588 16.373580\n", + "\n", + "[72 rows x 2 columns]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "\n", + "# df['time_epoch'] = (df['timestamp'].astype(np.int64)/100000000000).astype(np.int64)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "_cell_guid": "62a32613-6f21-46db-9212-0286be3d15dc", + "_execution_state": "idle", + "_uuid": "d4b909d1a62d7a5e2dd9f235d4fa31eb8d5a148f", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly with temperature repartition (viz 2)\n", + "a = df.loc[df['anomaly25'] == 0, 'value']\n", + "b = df.loc[df['anomaly25'] == 1, 'value']\n", + "\n", + "fig, axs = plt.subplots()\n", + "axs.hist([a,b], bins=32, stacked=True, color=['blue', 'red'], label = ['normal', 'anomaly'])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "2c56928d-e7fa-4342-8bc5-23527ae69747", + "_execution_state": "idle", + "_uuid": "9c616d8efd272ea7aeddee0d8bb2a41d803209cd", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 2.6 One class SVM\n", + "#### Use for collective anomalies (unordered).\n", + "Good for novelty detection (no anomalies in the train set). This algorithm performs well for multimodal data." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "_cell_guid": "16aea1c0-9a4c-489a-8b0c-b9e1893fba75", + "_execution_state": "idle", + "_uuid": "31b6408bed29fc271174502e699f12a3dd7deb9e", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 7198\n", + "1 69\n", + "Name: anomaly26, dtype: int64\n" + ] + } + ], + "source": [ + "# Take useful feature and standardize them \n", + "data = df[['value', 'hours', 'daylight', 'DayOfTheWeek', 'WeekDay']]\n", + "min_max_scaler = preprocessing.StandardScaler()\n", + "np_scaled = min_max_scaler.fit_transform(data)\n", + "# train one class SVM \n", + "model = OneClassSVM(nu=0.95 * outliers_fraction) #nu=0.95 * outliers_fraction + 0.05\n", + "data = pd.DataFrame(np_scaled)\n", + "model.fit(data)\n", + "# add the data to the main \n", + "df['anomaly26'] = pd.Series(model.predict(data))\n", + "df['anomaly26'] = df['anomaly26'].map( {1: 0, -1: 1} )\n", + "print(df['anomaly26'].value_counts())" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "_cell_guid": "c1ba12cb-4035-4082-9097-f9ebe4d5a550", + "_execution_state": "idle", + "_uuid": "a9cd247e4b1a081f538fe18305c98ed3e67429b3", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'anomaly26'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m~/.virtualenvs/posao_ai_39_TESTING/lib/python3.9/site-packages/pandas/core/indexes/base.py:3621\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 3620\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 3621\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_engine\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcasted_key\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3622\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n", + "File \u001b[0;32m~/.virtualenvs/posao_ai_39_TESTING/lib/python3.9/site-packages/pandas/_libs/index.pyx:136\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32m~/.virtualenvs/posao_ai_39_TESTING/lib/python3.9/site-packages/pandas/_libs/index.pyx:163\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:5198\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:5206\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n", + "\u001b[0;31mKeyError\u001b[0m: 'anomaly26'", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "Input \u001b[0;32mIn [40]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# visualisation of anomaly throughout time (viz 1)\u001b[39;00m\n\u001b[1;32m 2\u001b[0m fig, ax \u001b[38;5;241m=\u001b[39m plt\u001b[38;5;241m.\u001b[39msubplots()\n\u001b[0;32m----> 4\u001b[0m a \u001b[38;5;241m=\u001b[39m df\u001b[38;5;241m.\u001b[39mloc[\u001b[43mdf\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43manomaly26\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m1\u001b[39m, [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mtime_epoch\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mvalue\u001b[39m\u001b[38;5;124m'\u001b[39m]] \u001b[38;5;66;03m#anomaly\u001b[39;00m\n\u001b[1;32m 6\u001b[0m ax\u001b[38;5;241m.\u001b[39mplot(df[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mtime_epoch\u001b[39m\u001b[38;5;124m'\u001b[39m], df[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mvalue\u001b[39m\u001b[38;5;124m'\u001b[39m], color\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mblue\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 7\u001b[0m ax\u001b[38;5;241m.\u001b[39mscatter(a[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mtime_epoch\u001b[39m\u001b[38;5;124m'\u001b[39m],a[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mvalue\u001b[39m\u001b[38;5;124m'\u001b[39m], color\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mred\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", + "File \u001b[0;32m~/.virtualenvs/posao_ai_39_TESTING/lib/python3.9/site-packages/pandas/core/frame.py:3505\u001b[0m, in \u001b[0;36mDataFrame.__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3503\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcolumns\u001b[38;5;241m.\u001b[39mnlevels \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[1;32m 3504\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_getitem_multilevel(key)\n\u001b[0;32m-> 3505\u001b[0m indexer \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcolumns\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3506\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m is_integer(indexer):\n\u001b[1;32m 3507\u001b[0m indexer \u001b[38;5;241m=\u001b[39m [indexer]\n", + "File \u001b[0;32m~/.virtualenvs/posao_ai_39_TESTING/lib/python3.9/site-packages/pandas/core/indexes/base.py:3623\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 3621\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_engine\u001b[38;5;241m.\u001b[39mget_loc(casted_key)\n\u001b[1;32m 3622\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[0;32m-> 3623\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01merr\u001b[39;00m\n\u001b[1;32m 3624\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[1;32m 3625\u001b[0m \u001b[38;5;66;03m# If we have a listlike key, _check_indexing_error will raise\u001b[39;00m\n\u001b[1;32m 3626\u001b[0m \u001b[38;5;66;03m# InvalidIndexError. Otherwise we fall through and re-raise\u001b[39;00m\n\u001b[1;32m 3627\u001b[0m \u001b[38;5;66;03m# the TypeError.\u001b[39;00m\n\u001b[1;32m 3628\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_indexing_error(key)\n", + "\u001b[0;31mKeyError\u001b[0m: 'anomaly26'" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly throughout time (viz 1)\n", + "fig, ax = plt.subplots()\n", + "\n", + "a = df.loc[df['anomaly26'] == 1, ['time_epoch', 'value']] #anomaly\n", + "\n", + "ax.plot(df['time_epoch'], df['value'], color='blue')\n", + "ax.scatter(a['time_epoch'],a['value'], color='red')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "_cell_guid": "71e58d95-e373-40b4-b7a7-f153b994fdc5", + "_execution_state": "idle", + "_uuid": "5fd47274d72716015023ef9ea94ae54befa056b7", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\njo6eF/zMzMykmebtrZe0tDSlpKSou7tbzz33nL7yla8kuMro9NbLO++8k3Tjvrc+knHMS733snTp\n0qQc9x+bPXu27r//fi1ZsiRh4z6pArk3jz32mBYvXpzoMgaFbdu69NJL9eyzz+ryyy/X5s2bE11S\nvyxbtkzr169XTU2Nrr32Wj333HOJLikmO3fuVDAY1EMPPXTa7XYSfkLwzF66u7tVWVmp6667TpMm\nTUpwdbE5tZdkHven9pHsY/7UXpJ93O/YsUMbN27UAw88cNpYH8pxn9SB3NTUpLfeekv333+/SkpK\nFAqFNGfOnESX1W8jR47UxIkTJUnXX3+9Dhw4kOCK+uevf/2rrr32WknS5MmTtX///gRXFL3du3dr\n06ZNevrpp+Xz+ZSWlqbOzk5JJ59vZ54uMdmZvUjS4sWLdckll6i8vDzB1cXm1F7a29uTdtyf+Zgk\n85g/s5dkHff79+9XY2OjJGn06NHq7u7WBRdckJBxn9SBnJWVpZ07d+r555/X888/L8uyPvXu62Qy\nbdo07d69W9LJpUcvvfTSBFfUPyNHjux5Ydm3b58uueSSBFcUnWPHjmnFihXavHmz0tPTJZ18Yfl4\nOdja2lpNnTo1kSVGrbdeqqur5fF4NH/+/ARXF5sze0nWcd/bY5KsY763XpJ13L/66qvasmWLpJNX\nLGxvb0/YuE+qlbr279+v5cuX6/Dhw3K73crKytK6det6nhA33HCDfvOb3yS4yuj01ssTTzyhH/7w\nhwqHw0pLS9Py5cs1cuTIRJd6Tr31cd9992nFihXyeDwaMWKEHn30UQ0f3p9rVQ+tqqoqrVu37rQX\nxccff1xLly7Vhx9+qOzsbD322GPyeDwJrDI6vfXy3nvvafjw4T3ry1922WV65JFHElRh9HrrZfny\n5crOzpaUPOP+bH08/vjjSTXmpd57mT9/vlatWpV0476zs1MPPvigGhsb1dnZqfLyco0dO1YLFy4c\n8nGfVIEMAIBTJfWUNQAATkEgAwBgAAIZAAADEMgAABiAQAYAwAAEMgAABiCQAQAwAIEMAIAB/g9k\nQ013MabQngAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly with temperature repartition (viz 2)\n", + "a = df.loc[df['anomaly26'] == 0, 'value']\n", + "b = df.loc[df['anomaly26'] == 1, 'value']\n", + "\n", + "fig, axs = plt.subplots()\n", + "axs.hist([a,b], bins=32, stacked=True, color=['blue', 'red'], label=['normal', 'anomaly'])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "5b941a3a-1b7c-402a-9b3c-3de9cf50669b", + "_execution_state": "idle", + "_uuid": "379754b0742140516dde2963c36ea7db00139035", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Give result similar to isolation forest but find some anomalies in average values. Difficult to know if it's relevant.\n", + "## 2.7 RNN\n", + "#### Use for sequential anomalies (ordered)\n", + "RNN learn to recognize sequence in the data and then make prediction based on the previous sequence. We consider an anomaly when the next data points are distant from RNN prediction. Aggregation, size of sequence and size of prediction for anomaly are important parameters to have relevant detection. \n", + "Here we make learn from 50 previous values, and we predict just the 1 next value." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "_cell_guid": "dd04af3e-0b01-435a-b4fc-92666de95289", + "_execution_state": "idle", + "_uuid": "2fef8ac3eaa6116697b067f74be61a725898d784", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "#select and standardize data\n", + "data_n = df[['value', 'hours', 'daylight', 'DayOfTheWeek', 'WeekDay']]\n", + "min_max_scaler = preprocessing.StandardScaler()\n", + "np_scaled = min_max_scaler.fit_transform(data_n)\n", + "data_n = pd.DataFrame(np_scaled)\n", + "\n", + "# important parameters and train/test size\n", + "prediction_time = 1 \n", + "testdatasize = 1000\n", + "unroll_length = 50\n", + "testdatacut = testdatasize + unroll_length + 1\n", + "\n", + "#train data\n", + "x_train = data_n[0:-prediction_time-testdatacut].values\n", + "y_train = data_n[prediction_time:-testdatacut ][0].values\n", + "\n", + "# test data\n", + "x_test = data_n[0-testdatacut:-prediction_time].values\n", + "y_test = data_n[prediction_time-testdatacut: ][0].values" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "_cell_guid": "9ccb1c44-a714-45bd-8313-d6fc941bf220", + "_execution_state": "idle", + "_uuid": "be6f2d72557f6cb87179157efca44cdeab96b057", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x_train (6165, 50, 5)\n", + "y_train (6165,)\n", + "x_test (1000, 50, 5)\n", + "y_test (1000,)\n" + ] + } + ], + "source": [ + "#unroll: create sequence of 50 previous data points for each data points\n", + "def unroll(data,sequence_length=24):\n", + " result = []\n", + " for index in range(len(data) - sequence_length):\n", + " result.append(data[index: index + sequence_length])\n", + " return np.asarray(result)\n", + "\n", + "# adapt the datasets for the sequence data shape\n", + "x_train = unroll(x_train,unroll_length)\n", + "x_test = unroll(x_test,unroll_length)\n", + "y_train = y_train[-x_train.shape[0]:]\n", + "y_test = y_test[-x_test.shape[0]:]\n", + "\n", + "# see the shape\n", + "print(\"x_train\", x_train.shape)\n", + "print(\"y_train\", y_train.shape)\n", + "print(\"x_test\", x_test.shape)\n", + "print(\"y_test\", y_test.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "_cell_guid": "9089b29d-a698-407d-8675-089a8641e153", + "_execution_state": "idle", + "_uuid": "d2d991c7cb9ab07c73b329e9cb7378d5dd2a3212", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'tensorflow'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Input \u001b[0;32mIn [44]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# specific libraries for RNN\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m# keras is a high layer build on Tensorflow layer to stay in high level/easy implementation\u001b[39;00m\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mkeras\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlayers\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcore\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Dense, Activation, Dropout\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mkeras\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlayers\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mrecurrent\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LSTM\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mkeras\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmodels\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Sequential\n", + "File \u001b[0;32m~/.virtualenvs/posao_ai_39_TESTING/lib/python3.9/site-packages/keras/__init__.py:21\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[38;5;124;03m\"\"\"Implementation of the Keras API, the high-level API of TensorFlow.\u001b[39;00m\n\u001b[1;32m 16\u001b[0m \n\u001b[1;32m 17\u001b[0m \u001b[38;5;124;03mDetailed documentation and user guides are available at\u001b[39;00m\n\u001b[1;32m 18\u001b[0m \u001b[38;5;124;03m[keras.io](https://keras.io).\u001b[39;00m\n\u001b[1;32m 19\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 20\u001b[0m \u001b[38;5;66;03m# pylint: disable=unused-import\u001b[39;00m\n\u001b[0;32m---> 21\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtensorflow\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpython\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m tf2\n\u001b[1;32m 22\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mkeras\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m distribute\n\u001b[1;32m 24\u001b[0m \u001b[38;5;66;03m# See b/110718070#comment18 for more details about this import.\u001b[39;00m\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'tensorflow'" + ] + } + ], + "source": [ + "# specific libraries for RNN\n", + "# keras is a high layer build on Tensorflow layer to stay in high level/easy implementation\n", + "from keras.layers.core import Dense, Activation, Dropout\n", + "from keras.layers.recurrent import LSTM\n", + "from keras.models import Sequential\n", + "import time #helper libraries\n", + "from keras.models import model_from_json\n", + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "_cell_guid": "33166362-a1a8-4adb-a6fc-afb10cb8b1b4", + "_execution_state": "idle", + "_uuid": "bd87547d94e2d128a755c080e39386dfe642e8b8", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:7: UserWarning: The `input_dim` and `input_length` arguments in recurrent layers are deprecated. Use `input_shape` instead.\n", + " import sys\n", + "/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:7: UserWarning: Update your `LSTM` call to the Keras 2 API: `LSTM(return_sequences=True, input_shape=(None, 5), units=50)`\n", + " import sys\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "compilation time : 0.01883721351623535\n" + ] + } + ], + "source": [ + "# Build the model\n", + "model = Sequential()\n", + "\n", + "model.add(LSTM(\n", + " input_dim=x_train.shape[-1],\n", + " output_dim=50,\n", + " return_sequences=True))\n", + "model.add(Dropout(0.2))\n", + "\n", + "model.add(LSTM(\n", + " 100,\n", + " return_sequences=False))\n", + "model.add(Dropout(0.2))\n", + "\n", + "model.add(Dense(\n", + " units=1))\n", + "model.add(Activation('linear'))\n", + "\n", + "start = time.time()\n", + "model.compile(loss='mse', optimizer='rmsprop')\n", + "print('compilation time : {}'.format(time.time() - start))" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "_cell_guid": "8e70774e-68d7-46ed-8434-edefc1db397f", + "_execution_state": "busy", + "_uuid": "5adb8e2b1b7d1f7d541b6b306b8d98885b37500a", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.6/site-packages/Keras-2.0.6-py3.6.egg/keras/models.py:846: UserWarning: The `nb_epoch` argument in `fit` has been renamed `epochs`.\n", + " warnings.warn('The `nb_epoch` argument in `fit` '\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train on 5548 samples, validate on 617 samples\n", + "Epoch 1/30\n", + "5548/5548 [==============================] - 33s - loss: 0.7767 - val_loss: 0.8373\n", + "Epoch 2/30\n", + "5548/5548 [==============================] - 23s - loss: 0.3013 - val_loss: 0.4080\n", + "Epoch 3/30\n", + "5548/5548 [==============================] - 25s - loss: 0.2139 - val_loss: 0.3102\n", + "Epoch 4/30\n", + "5548/5548 [==============================] - 21s - loss: 0.1265 - val_loss: 0.2580\n", + "Epoch 5/30\n", + "5548/5548 [==============================] - 21s - loss: 0.1034 - val_loss: 0.2415\n", + "Epoch 6/30\n", + "5548/5548 [==============================] - 20s - loss: 0.1015 - val_loss: 0.2464\n", + "Epoch 7/30\n", + "5548/5548 [==============================] - 22s - loss: 0.1013 - val_loss: 0.2389\n", + "Epoch 8/30\n", + "5548/5548 [==============================] - 20s - loss: 0.1032 - val_loss: 0.2162\n", + "Epoch 9/30\n", + "5548/5548 [==============================] - 21s - loss: 0.1072 - val_loss: 0.1966\n", + "Epoch 10/30\n", + "5548/5548 [==============================] - 22s - loss: 0.0895 - val_loss: 0.1803\n", + "Epoch 11/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0849 - val_loss: 0.1688\n", + "Epoch 12/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0810 - val_loss: 0.1790\n", + "Epoch 13/30\n", + "5548/5548 [==============================] - 19s - loss: 0.0866 - val_loss: 0.1847\n", + "Epoch 14/30\n", + "5548/5548 [==============================] - 19s - loss: 0.0926 - val_loss: 0.1943\n", + "Epoch 15/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0883 - val_loss: 0.1561\n", + "Epoch 16/30\n", + "5548/5548 [==============================] - 19s - loss: 0.0809 - val_loss: 0.1465\n", + "Epoch 17/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0777 - val_loss: 0.1425\n", + "Epoch 18/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0784 - val_loss: 0.1307\n", + "Epoch 19/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0800 - val_loss: 0.1218\n", + "Epoch 20/30\n", + "5548/5548 [==============================] - 19s - loss: 0.0751 - val_loss: 0.1219\n", + "Epoch 21/30\n", + "5548/5548 [==============================] - 22s - loss: 0.0686 - val_loss: 0.1225\n", + "Epoch 22/30\n", + "5548/5548 [==============================] - 21s - loss: 0.0714 - val_loss: 0.1183\n", + "Epoch 23/30\n", + "5548/5548 [==============================] - 22s - loss: 0.0875 - val_loss: 0.1176\n", + "Epoch 24/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0730 - val_loss: 0.1142\n", + "Epoch 25/30\n", + "5548/5548 [==============================] - 21s - loss: 0.0701 - val_loss: 0.1135\n", + "Epoch 26/30\n", + "5548/5548 [==============================] - 20s - loss: 0.0661 - val_loss: 0.1050\n", + "Epoch 27/30\n", + "5548/5548 [==============================] - 21s - loss: 0.0656 - val_loss: 0.1077\n", + "Epoch 28/30\n", + "5548/5548 [==============================] - 21s - loss: 0.0701 - val_loss: 0.1128\n", + "Epoch 29/30\n", + "5548/5548 [==============================] - 22s - loss: 0.0690 - val_loss: 0.1123\n", + "Epoch 30/30\n", + "5548/5548 [==============================] - 21s - loss: 0.0736 - val_loss: 0.1131\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Train the model\n", + "#nb_epoch = 350\n", + "\n", + "model.fit(\n", + " x_train,\n", + " y_train,\n", + " batch_size=3028,\n", + " nb_epoch=30,\n", + " validation_split=0.1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "_cell_guid": "2f77488f-2a11-4abf-9d5d-28106b9b7543", + "_execution_state": "idle", + "_uuid": "53c665cfadb466197cdbd5c4390e37fd8bf9084e", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'\\n# serialize model to JSON\\nmodel_json = model.to_json()\\nwith open(\"model2.json\", \"w\") as json_file:\\n json_file.write(model_json)\\n# serialize weights to HDF5\\nmodel.save_weights(\"model2.h5\")\\nprint(\"Saved model to disk\")\\n'" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# save the model because the training is long (1h30) and we don't want to do it every time\n", + "\"\"\"\n", + "# serialize model to JSON\n", + "model_json = model.to_json()\n", + "with open(\"model2.json\", \"w\") as json_file:\n", + " json_file.write(model_json)\n", + "# serialize weights to HDF5\n", + "model.save_weights(\"model2.h5\")\n", + "print(\"Saved model to disk\")\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "_cell_guid": "7f4b7842-5081-4f20-a082-d0f971949c34", + "_execution_state": "idle", + "_uuid": "db2f64492cc4929317153ecb4d0140f2f28c1e90", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'\\njson_file = open(\\'model.json\\', \\'r\\')\\nloaded_model_json = json_file.read()\\njson_file.close()\\nloaded_model = model_from_json(loaded_model_json)\\n# load weights into new model\\nloaded_model.load_weights(\"model.h5\")\\nprint(\"Loaded model from disk\")\\n'" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# load json and create model\n", + "\"\"\"\n", + "json_file = open('model.json', 'r')\n", + "loaded_model_json = json_file.read()\n", + "json_file.close()\n", + "loaded_model = model_from_json(loaded_model_json)\n", + "# load weights into new model\n", + "loaded_model.load_weights(\"model.h5\")\n", + "print(\"Loaded model from disk\")\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "_cell_guid": "adb69c0d-ff41-49c5-bf30-335f85ef0ec5", + "_execution_state": "idle", + "_uuid": "f65429b7f2aed953e7db2c3276e4fcdd764df476", + "collapsed": true, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# create the list of difference between prediction and test data\n", + "loaded_model = model\n", + "diff=[]\n", + "ratio=[]\n", + "p = loaded_model.predict(x_test)\n", + "# predictions = lstm.predict_sequences_multiple(loaded_model, x_test, 50, 50)\n", + "for u in range(len(y_test)):\n", + " pr = p[u][0]\n", + " ratio.append((y_test[u]/pr)-1)\n", + " diff.append(abs(y_test[u]- pr))" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "_cell_guid": "35249507-6673-4ac0-9196-b75f1cd5c395", + "_execution_state": "idle", + "_uuid": "2a0eb8a0ad91203f2797eb0602d6143bec46b96e", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAecAAAFMCAYAAADx1nR5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsfXmYXFWd9nvuUre2XpPOgkFBXJCIQIARjIBso8OmjCjg\nqKiM6CeO4xhE9HOQGQGHz2VG0XHBDYFBRMFRZMRRIyKEJYR9JwnZ0+lOeqv9bt8fZ7nn3LrV3emu\n2+lOzvs8edJVdevWudt5z/tbSRiGITQ0NDQ0NDRmDYw9PQANDQ0NDQ0NFZqcNTQ0NDQ0Zhk0OWto\naGhoaMwyaHLW0NDQ0NCYZdDkrKGhoaGhMcugyVlDQ0NDQ2OWYVrk/Pzzz+OUU07BjTfe2PTZfffd\nh3POOQfnnnsuvvWtb03nZzQ0NDQ0NPYpTJmcK5UKvvjFL+LYY49N/PzKK6/Etddei5tvvhn33nsv\nXnzxxSkPUkNDQ0NDY1+CNdUvZjIZXHfddbjuuuuaPtu0aRO6urqwePFiAMAJJ5yAVatW4VWvelXL\n/Q0MjE11KIno6cljaKjS1n3ui9DncfrQ53D60Odw+tDnsD1o53ns6+to+dmUydmyLFhW8tcHBgbQ\n29srXvf29mLTpk3j7q+nJw/LMqc6nESMd+Aak4c+j9OHPofThz6H04c+h+3BTJzHKZNzu9HuFV1f\nX0fb1fi+CH0epw99DqcPfQ6nD30O24N2nsfxSD6VaO0FCxZgcHBQvO7v78eCBQvS+CkNDQ0NDY29\nDqmQ85IlS1AqlbB582Z4noeVK1di+fLlafyUhoaGhobGXocpm7WffPJJXHPNNdiyZQssy8Jdd92F\nk046CUuWLMGpp56KK664AitWrAAAnHbaaTjwwAPbNmgNDQ0NDY29GWS2tIxsty9E+1faA30epw99\nDqcPfQ6nD30O24M57XPW0NDQ0NDQmDo0OWtoaGhoaMwyaHLW0NDQ0NCYZdDkPAP4/OcvxZo1q3Hn\nnb/G3XevbLndypW/BwDcf/99uP32n8/U8DQ0NDQ0ZhlmTRGSfQGnnXZmy89c18Utt/wXTjzxFBxz\nzJtmcFQaGhoaGrMNc4acC1d8Hs6vfzn5LxgEvcH4gej1M9+B8hVXjrvNnXf+Gg88cB/K5TIGBnbg\n3e9+D2644Uc45pjl6Onpwemnn4UvfemL8DwXhmHgM5/5ZyxatAg33XQ9fv/7u7Bo0WKUy2UAwA9+\n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\nFJwJIYQQmfl/39IaMeFY7goAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# plot the prediction and the reality (for the test data)\n", + "fig, axs = plt.subplots()\n", + "axs.plot(p,color='red', label='prediction')\n", + "axs.plot(y_test,color='blue', label='y_test')\n", + "plt.legend(loc='upper left')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "_cell_guid": "7011b0e3-e9b6-4ea0-bb0e-1c44b11ad950", + "_execution_state": "idle", + "_uuid": "0b6f73d8f09bdd06ced35203de4250807084122d", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 7257\n", + "1 10\n", + "Name: anomaly27, dtype: int64\n" + ] + } + ], + "source": [ + "# select the most distant prediction/reality data points as anomalies\n", + "diff = pd.Series(diff)\n", + "number_of_outliers = int(outliers_fraction*len(diff))\n", + "threshold = diff.nlargest(number_of_outliers).min()\n", + "# data with anomaly label (test data part)\n", + "test = (diff >= threshold).astype(int)\n", + "# the training data part where we didn't predict anything (overfitting possible): no anomaly\n", + "complement = pd.Series(0, index=np.arange(len(data_n)-testdatasize))\n", + "# # add the data to the main\n", + "df['anomaly27'] = complement.append(test, ignore_index='True')\n", + "print(df['anomaly27'].value_counts())" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "_cell_guid": "c5230ec9-70cc-48de-aab0-b1f79c442a60", + "_execution_state": "idle", + "_uuid": "eea00d2cae4842b1ac445a8252844fb291905b4f", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "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\nq31Y/Ur0AAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly throughout time (viz 1)\n", + "fig, ax = plt.subplots()\n", + "\n", + "a = df.loc[df['anomaly27'] == 1, ['time_epoch', 'value']] #anomaly\n", + "\n", + "ax.plot(df['time_epoch'], df['value'], color='blue')\n", + "ax.scatter(a['time_epoch'],a['value'], color='red')\n", + "plt.axis([1.370*1e7, 1.405*1e7, 15,30])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "_cell_guid": "7ab6f44f-f316-406c-b72b-069fb1aa162f", + "_execution_state": "idle", + "_uuid": "4400b2bf187ba69b7616f19eee23bc2277c2d766", + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.6/site-packages/matplotlib/axes/_axes.py:545: UserWarning: No labelled objects found. Use label='...' kwarg on individual plots.\n", + " warnings.warn(\"No labelled objects found. \"\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# visualisation of anomaly with temperature repartition (viz 2)\n", + "a = df.loc[df['anomaly27'] == 0, 'value']\n", + "b = df.loc[df['anomaly27'] == 1, 'value']\n", + "\n", + "fig, axs = plt.subplots()\n", + "axs.hist([a,b], bins=32, stacked=True, color=['blue', 'red'])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_cell_guid": "43e8a0e9-63eb-462c-a08b-996cb27dfa5f", + "_execution_state": "idle", + "_uuid": "6a419593698fe1d6ef85deec4401b1767e7d4538", + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 2.8 Collective and sequential anomalies (Ordered)\n", + "This class is most general and consider ordering as well as value combinations. We usually use combination of algorithm like cluster+markov model.\n", + "## 3 Result comparison\n", + "(may be later)\n", + "## 4 Conclusion\n", + "For this case, the contextual anomaly detection (categories+elliptique enveloppe) seem a good solution. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/0003/package.json b/examples/0003/package.json new file mode 100644 index 0000000..d48bfdc --- /dev/null +++ b/examples/0003/package.json @@ -0,0 +1,23 @@ +{ + "private": true, + "dependencies": { + "@hat-core/future": "^0.4.1-dev20210707", + "@hat-core/juggler": "^0.4.1-dev20210707", + "@hat-core/renderer": "^0.4.1-dev20210707", + "@reduxjs/toolkit": "^1.8.3", + "bootstrap": "^4.6.0", + "css-loader": "^5.2.6", + "plotly": "^1.0.6", + "plotly.js": "^2.7.0", + "redux": "^4.2.0", + "resolve-url-loader": "^4.0.0", + "sass": "^1.34.1", + "sass-loader": "11.0.1", + "style-loader": "^2.0.0", + "webpack": "^5.38.1" + }, + "devDependencies": { + "snabbdom": "2.1.0", + "webpack-cli": "^4.7.2" + } +} diff --git a/examples/0003/requirements.txt b/examples/0003/requirements.txt new file mode 100644 index 0000000..91988fd --- /dev/null +++ b/examples/0003/requirements.txt @@ -0,0 +1,47 @@ +aimm==1.2.dev0 +aiohappyeyeballs==2.4.0 +aiohttp==3.10.5 +aiosignal==1.3.1 +appdirs==1.4.4 +attrs==24.2.0 +frozenlist==1.4.1 +hat-aio==0.7.10 +hat-asn1==0.6.8 +hat-drivers==0.8.9 +hat-event==0.9.20 +hat-gateway==0.6.11 +hat-gui==0.7.9 +hat-json==0.5.28 +hat-juggler==0.6.21 +hat-monitor==0.8.11 +hat-orchestrator==0.7.4 +hat-peg==0.5.9 +hat-sbs==0.7.2 +hat-syslog==0.7.18 +hat-util==0.6.16 +idna==3.10 +joblib==1.4.2 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.22.0 +jsonschema-specifications==2023.12.1 +lmdb==1.4.1 +multidict==6.1.0 +numpy==2.1.1 +pandas==2.2.2 +psutil==6.0.0 +pyserial==3.5 +python-dateutil==2.9.0.post0 +pytz==2024.2 +PyYAML==6.0.2 +referencing==0.35.1 +rpds-py==0.20.0 +scikit-learn==1.5.2 +scipy==1.14.1 +six==1.16.0 +tenacity==9.0.0 +threadpoolctl==3.5.0 +tomli==2.0.1 +tomli_w==1.0.0 +tzdata==2024.1 +yarl==1.11.1 diff --git a/examples/0003/run.sh b/examples/0003/run.sh new file mode 100755 index 0000000..4af7d23 --- /dev/null +++ b/examples/0003/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +rm -rf build/ + +yarn install --silent +node_modules/.bin/webpack --config webpack.config.js + +rm data/aimm.db -f +export PYTHONPATH=src_py +python -m hat.orchestrator.main --conf ./conf/orchestrator.yaml diff --git a/examples/0003/src_js/views/login/index.js b/examples/0003/src_js/views/login/index.js new file mode 100644 index 0000000..8900f3b --- /dev/null +++ b/examples/0003/src_js/views/login/index.js @@ -0,0 +1,14 @@ +export function vt() { + return ['div.login', 'Loading...']; +} + + +export function init() { + setTimeout(() => { + hat.conn._conn.send({ + type: 'login', + name: 'user', + password: 'pass' + }); + }, 0); +} diff --git a/examples/0003/src_js/views/main/index.js b/examples/0003/src_js/views/main/index.js new file mode 100644 index 0000000..3431791 --- /dev/null +++ b/examples/0003/src_js/views/main/index.js @@ -0,0 +1,185 @@ +import * as plotly from 'plotly.js/dist/plotly.js'; +import 'main/index.scss'; +import redux from 'redux'; + + +export function vt() { + return plot(); +} + +export function plot() { + let l = r.get('remote', 'timeseries','timestamps','reading').length; + + const dateValue = new Date(r.get('remote', 'timeseries','timestamps','reading')[l-1]); + dateValue.setHours(dateValue.getHours() + 48); + const x_r = dateValue.getFullYear()+ "-" + (dateValue.getMonth()+1) + "-" + dateValue.getDate() + " " + + dateValue.getHours() + ":00:00"; + + const layout = { + title: 'Timeseries anomaly/forecast model testing', + xaxis: { + title: 'Timestamp', + showgrid: true, + range: [ + r.get('remote', 'timeseries','timestamps','reading')[0], + x_r + ] + }, + yaxis: { + title: 'Temperature', + showline: false, + range: [0, 30] + } + }; + const config = { + displayModeBar: true, + responsive: true, + staticPlot: true + }; + + const reading_trace = { + x: r.get('remote', 'timeseries','timestamps','reading'), + y: r.get('remote', 'timeseries','values','reading'), + line: { shape: 'spline' }, + type: 'scatter', + name: 'Reading' + }; + + const anomaly_trace = { + x: r.get('remote', 'timeseries','timestamps','anomaly'), + y: r.get('remote', 'timeseries','values','anomaly'), + mode: 'markers', + type: 'scatter', + name: 'Anomaly' + }; + + const forecast_trace = { + x: r.get('remote', 'timeseries','timestamps','forecast').map((k) => { + const dateValue = new Date(k); + + dateValue.setHours(dateValue.getHours() + 24); + const date = dateValue.getFullYear()+ "-" + (dateValue.getMonth()+1) + "-" + dateValue.getDate(); + const hour = dateValue.getHours() + ":00:00"; + return date + " " + hour; + + }), + y: r.get('remote', 'timeseries','values','forecast'), + line: { shape: 'spline' }, + type: 'scatter', + name: 'Forecast' + }; + + const data = [reading_trace, anomaly_trace,forecast_trace]; + + const cur_anomaly_model_name = r.get('remote','timeseries','info','anomaly','new_current_model'); + const cur_forecast_model_name = r.get('remote','timeseries','info','forecast','new_current_model'); + + + const setting_name = r.get('remote','timeseries','info','anomaly','setting','name'); + + function on_radio_switch(model_type,prediction_type){ + console.log("Picked: " + model_type); + hat.send( + 'timeseries', + 'model-change', + {'action': 'model_change','type':prediction_type, 'model': model_type} + ); + } + + function change_button_color(model_type,prediction_type){ + const models = r.get('remote','timeseries','info',prediction_type, 'model_state','models'); + if (!models) return ''; + for (const [key, value] of Object.entries(models)) { + if (value.split(".").at(-1) === model_type ) + return 'border: 3px solid green;' + } + return ''; + } + + const generate_setting_inputs = function (prediction_type) { + if (!r.get('remote','timeseries','info',prediction_type,'setting')) return []; + + var cur_model_name = prediction_type === 'anomaly'? cur_anomaly_model_name:cur_forecast_model_name; + + var t = Object.entries(r.get('remote','timeseries','info',prediction_type,'setting')) + .map(function([key,value],index) { + return ["div",[ + ["label",{props: {for: 'input1'}}, key ], + ["input",{ + props: { + disabled: !cur_model_name, + id: 'input1', + value: value }, + on: { + change: function (e) { + console.log("changed to: " + e.target.value); + hat.conn.send('timeseries', + { + 'action': 'setting_change','type':prediction_type, + [key]: e.target.value}); + } + } + }], + ]] + }); + + return ["div",t]; + } + + const generate_model_buttons = function (prediction_type) { + if (!r.get('remote','timeseries','info',prediction_type,'supported_models')) return []; + + const cur_model_name = prediction_type === 'anomaly' ? cur_anomaly_model_name : cur_forecast_model_name; + + const t = r.get('remote', 'timeseries', 'info', prediction_type, 'supported_models').map(function (value, index) { + return [ + "button", + { + props: { + disabled: (cur_model_name || "").endsWith(value) || value === 'Linear' || value === 'Cluster', + type: 'checkbox', id: 'id1', + name: 'modelSelect', + style: change_button_color(value, prediction_type), + value: 'Forest' + }, + on: {click: () => on_radio_switch(value, prediction_type)} + }, + value + ] + }); + return ["div",t]; + } + + const generate_div = function (prediction_type){ + const cur_model_name = prediction_type === 'anomaly' ? cur_anomaly_model_name : cur_forecast_model_name; + return ['div', + {props: { + id: prediction_type + '_div', + }}, + ["label",{props: {for: 'input2'}},' Current '+prediction_type+' model '], + ["label", {props: {style: "font-weight: bold; color: green;"}}, cur_model_name || ''], + ["br"], + generate_setting_inputs(prediction_type), + ["br"], + ["label",{props: {for: prediction_type+'_buttons'}},prediction_type+' models: '], + ['div',{props: {id: prediction_type+'_buttons'}},generate_model_buttons(prediction_type)] + ]; + } + + return ['div.main', + ['div.settings', generate_div('anomaly'), generate_div('forecast')], + ['div.plot', + { + plotData: data, + hook: { + insert: vnode => plotly.newPlot(vnode.elm, data, layout, config), + update: (oldVnode, vnode) => { + if (u.equals(oldVnode.data.plotData, vnode.data.plotData)) return; + plotly.react(vnode.elm, data, layout, config); + }, + destroy: vnode => plotly.purge(vnode.elm) + } + } + ] + ]; +} diff --git a/examples/0003/src_py/air_supervision/adapters/timeseries.py b/examples/0003/src_py/air_supervision/adapters/timeseries.py new file mode 100644 index 0000000..d9dc9d2 --- /dev/null +++ b/examples/0003/src_py/air_supervision/adapters/timeseries.py @@ -0,0 +1,212 @@ +from datetime import datetime, timedelta +from collections import deque +import hat.aio +import hat.event.common +import hat.gui.common +import hat.util +import logging + + +mlog = logging.getLogger(__name__) + + +async def create_subscription(_): + return hat.event.common.create_subscription( + [("gui", "system", "timeseries", "*"), ("gui", "log", "*")] + ) + + +class Adapter(hat.gui.common.Adapter): + def __init__(self, _, event_client): + self._async_group = hat.aio.Group() + self._event_client = event_client + self._session = set() + + self._models_info = {"anomaly": {}, "forecast": {}} + self._series_values = { + "reading": deque(maxlen=72), + "anomaly": deque(maxlen=21), + "forecast": [], + } + self._series_timestamps = { + "reading": deque(maxlen=72), + "anomaly": deque(maxlen=21), + "forecast": [], + } + + self._state_change_cb_registry = hat.util.CallbackRegistry() + + @property + def async_group(self): + return self._async_group + + @property + def series_values(self): + return self._series_values + + @property + def series_timestamps(self): + return self._series_timestamps + + @property + def models_info(self): + return self._models_info + + async def create_session(self, user, roles, state, notify_cb): + session = Session( + self, + state, + notify_cb, + self._async_group.create_subgroup(), + self._event_client, + ) + self._state_change_cb_registry.register(session.on_state_change) + session.on_state_change() + return session + + async def process_events(self, events): + for event in events: + if event.type[1] == "log": + """ + Additional data for GUI. Just pass it through, JS will handle + it. + """ + self._models_info[event.type[2]] = dict( + self._models_info[event.type[2]], + **{event.type[3]: event.payload.data} + ) + else: + await self._update_series(event) + self._state_change_cb_registry.notify() + + async def _update_series(self, event): + """ + # Data is from reading OR forecast OR anomaly + + Data has the following structure: + { + 'timestamp': datetime.datetime(...), + 'value': original y value, + 'result': result from model + } + + Anomaly: + 'result' is a number 0 or 1, save 'value' if 'result' == 1 + Forecast: + 'result' is a predicted value y,always save 'value' + Reading: + 'result' DOESN'T EXIST + """ + + series_id = event.type[-1] + if series_id == "anomaly" and event.payload.data["result"] <= 0: + return + value_key = "result" if series_id == "forecast" else "value" + self._series_values[series_id].append(event.payload.data[value_key]) + + self._series_timestamps[series_id].append( + datetime.strptime( + event.payload.data["timestamp"], "%Y-%m-%d %H:%M:%S" + ) + ) + + forecast_t = self._series_timestamps["forecast"] + if not forecast_t: + return + forecast_v = self._series_values["forecast"] + forecast_v, forecast_t = _truncate_lists(forecast_v, forecast_t) + + oldest_forecast = max(self._series_timestamps["reading"]) - timedelta( + days=2 + ) + if min(forecast_t) < oldest_forecast: + forecast_t = [i for i in forecast_t if i >= oldest_forecast] + forecast_v = forecast_v[-len(forecast_t) :] + + self._series_values["forecast"] = forecast_v + self._series_timestamps["forecast"] = forecast_t + + +class Session(hat.gui.common.AdapterSession): + def __init__(self, adapter, state, notify_cb, group, event_client): + self._adapter = adapter + self._state = state + self._notify_cb = notify_cb + self._async_group = group + self._event_client = event_client + + async def process_request(self, name, data): + """Refreshes state dictionary and loops through events + Adapter received from JS. If such events exist, + adapter passes them to the Module (creates new events for Module + component). + """ + event_type = { + "setting_change": ("user_action", data["type"], "setting_change"), + "model_change": ("user_action", data["type"], "model_change"), + }[data["action"]] + event = hat.event.common.RegisterEvent( + type=event_type, + source_timestamp=None, + payload=hat.event.common.EventPayloadJson( + data=data, + ), + ) + await self._event_client.register(([event])) + + @property + def async_group(self): + return self._async_group + + def on_state_change(self): + self._state.set( + [], + { + "values": { + k: list(v) for k, v in self._adapter.series_values.items() + }, + "timestamps": { + k: [str(v) for v in series] + for k, series in self._adapter.series_timestamps.items() + }, + "info": { + "anomaly": self._adapter.models_info["anomaly"], + "forecast": self._adapter.models_info["forecast"], + }, + }, + ) + + +def _truncate_lists(vals, tss): + """ + Truncates lists vals and tss so that timestamps don't repeat. + For example: + vals = [20,21,25,23,28,19,22,26,21] + tss = [1,2,3,4,5,3,4,5,6] + + will be truncated to: + vals = [20, 21, 25, 23, 28, 21] + tss = [1, 2, 3, 4, 5, 6] + """ + + max_ts = tss[0] + index = 0 + for ts in tss: + if ts < max_ts: + break + max_ts = ts + index = index + 1 + + # index of element with value equal to max_ts + index_last = len(tss) - list(reversed(tss)).index(max_ts) + + # delete elements from index_first to index + del vals[index + 1 : index_last] + del tss[index + 1 : index_last] + + return vals, tss + + +info = hat.gui.common.AdapterInfo( + create_subscription=create_subscription, create_adapter=Adapter +) diff --git a/examples/0003/src_py/air_supervision/aimm/anomaly.py b/examples/0003/src_py/air_supervision/aimm/anomaly.py new file mode 100644 index 0000000..05bb5fc --- /dev/null +++ b/examples/0003/src_py/air_supervision/aimm/anomaly.py @@ -0,0 +1,163 @@ +from sklearn import exceptions +import aimm.plugins +import numpy as np +import pickle + + +import pandas as pd +from sklearn.ensemble import IsolationForest +from sklearn import preprocessing +from sklearn.decomposition import PCA +from sklearn.cluster import KMeans +from sklearn.svm import OneClassSVM + + +class GenericAnomalyModel(aimm.plugins.Model): + def __init__(self, **kwargs): + self.scale_ = -1 + self.mean_ = -1 + self.model = None + self.hyperparameters = {} + + def predict(self, x): + x = pd.DataFrame((x - self.mean_) / self.scale_) + series = pd.Series(self.model.predict(x)) + return series.map({1: 0, -1: 1}).values.tolist() + + def fit(self, x, y, **kwargs): + self.model.fit(self._scale(x)) + return self + + def serialize(self): + return pickle.dumps(self) + + @classmethod + def deserialize(cls, b): + return pickle.loads(b) + + def _scale(self, x): + min_max_scaler = preprocessing.StandardScaler() + + self.mean_ = np.mean(x, axis=0) + self.scale_ = np.std(x, axis=0) + + return pd.DataFrame(min_max_scaler.fit_transform(x)) + + def _update_hp(self, **kwargs): + changed = False + for key, value in kwargs.items(): + if key in self.hyperparameters: + self.hyperparameters[key] = float(value) + changed = True + return changed + + +@aimm.plugins.model +class Forest(GenericAnomalyModel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + if not self._update_hp(**kwargs): + self.hyperparameters = {"contamination": 0.01} + + self.model = IsolationForest( + contamination=self.hyperparameters["contamination"] + ) + + def fit(self, x, y, **kwargs): + if self._update_hp(**kwargs): + self.model = IsolationForest( + contamination=self.hyperparameters["contamination"] + ) + + return super().fit(x, y, **kwargs) + + +@aimm.plugins.model +class SVM(GenericAnomalyModel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + if not self._update_hp(**kwargs): + self.hyperparameters = {"contamination": 0.05} + + self.model = OneClassSVM( + nu=0.95 * self.hyperparameters["contamination"] + ) + # nu=0.95 * outliers_fraction + 0.05 + + def fit(self, x, y, **kwargs): + if self._update_hp(**kwargs): + self.model = OneClassSVM( + nu=0.95 * self.hyperparameters["contamination"] + ) + + return super().fit(x, y, **kwargs) + + +@aimm.plugins.model +class Cluster(GenericAnomalyModel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def fit(self, x, y, **kwargs): + data = x[["value", "hours", "daylight", "DayOfTheWeek", "WeekDay"]] + + outliers_fraction = 0.01 + + min_max_scaler = preprocessing.StandardScaler() + np_scaled = min_max_scaler.fit_transform(data) + data = pd.DataFrame(np_scaled) + # reduce to 2 important features + pca = PCA(n_components=2) + data = pca.fit_transform(data) + # standardize these 2 new features + min_max_scaler = preprocessing.StandardScaler() + np_scaled = min_max_scaler.fit_transform(data) + data = pd.DataFrame(np_scaled) + + # calculate with different number of centroids to see the loss plot + # (elbow method) + n_cluster = range(1, 20) + kmeans = [KMeans(n_clusters=i).fit(data) for i in n_cluster] + + # Not clear for me, I choose 15 centroids arbitrarily and add these + # data to the central dataframe + x["cluster"] = kmeans[14].predict(data) + x["principal_feature1"] = data[0] + x["principal_feature2"] = data[1] + + # return Series of distance between each point and his distance with + # the closest centroid + def get_distance_by_point(data, model): + result = pd.Series() + for i in range(0, len(data)): + Xa = np.array(data.loc[i]) + Xb = model.cluster_centers_[model.labels_[i] - 1] + result.at[i] = np.linalg.norm(Xa - Xb) + return result + + # get the distance between each point and its nearest centroid. The + # biggest distances are considered as anomaly + distance = get_distance_by_point(data, kmeans[14]) + number_of_outliers = int(outliers_fraction * len(distance)) + threshold = distance.nlargest(number_of_outliers).min() + # anomaly21 contain the anomaly result of method + # 2.1 Cluster (0:normal, 1:anomaly) + x["anomaly21"] = int(distance >= threshold) + + return self + + def predict(self, x): + try: + x = np.array(x).reshape(1, -1) + return self.model.predict(x).reshape(-1).tolist() + except exceptions.NotFittedError: + return [] + + def serialize(self): + return pickle.dumps(self) + + @classmethod + def deserialize(self, b): + return pickle.loads(b) diff --git a/examples/0003/src_py/air_supervision/aimm/forecast.py b/examples/0003/src_py/air_supervision/aimm/forecast.py new file mode 100644 index 0000000..73648f0 --- /dev/null +++ b/examples/0003/src_py/air_supervision/aimm/forecast.py @@ -0,0 +1,116 @@ +from sklearn import multioutput, svm, exceptions +import aimm.plugins +import numpy as np +import pickle +from sklearn.linear_model import LinearRegression + + +@aimm.plugins.model +class MultiOutputSVR(aimm.plugins.Model): + def __init__(self, **kwargs): + self.hyperparameters = {} + + if not self._update_hp(**kwargs): + self.hyperparameters = {"C": 2000} + self.model = multioutput.MultiOutputRegressor( + svm.SVR(C=self.hyperparameters["C"]) + ) + + def fit(self, x, y, **kwargs): + if self._update_hp(**kwargs): + self.model = multioutput.MultiOutputRegressor( + svm.SVR(C=self.hyperparameters["C"]) + ) + + self.model.fit(x, y) + return self + + def predict(self, x): + try: + x = np.array(x).reshape(1, -1) + return self.model.predict(x).reshape(-1).tolist() + except exceptions.NotFittedError: + return [] + + def serialize(self): + return pickle.dumps(self) + + @classmethod + def deserialize(cls, b): + return pickle.loads(b) + + def _update_hp(self, **kwargs): + changed = False + for key, value in kwargs.items(): + if key in self.hyperparameters: + self.hyperparameters[key] = float(value) + changed = True + return changed + + +@aimm.plugins.model +class Linear(aimm.plugins.Model): + def __init__(self, **kwargs): + self.hyperparameters = {} + self._update_hp(**kwargs) + self._linear = LinearRegression() + + def fit(self, x, y): + self._linear = self._linear.fit(x, y) + return self + + def predict(self, x, **kwargs): + return self._linear.predict(x).reshape(-1).tolist() + + def serialize(self): + return pickle.dumps(self) + + @classmethod + def deserialize(cls, b): + return pickle.loads(b) + + def _update_hp(self, **kwargs): + changed = False + for key, value in kwargs.items(): + if key in self.hyperparameters: + self.hyperparameters[key] = float(value) + changed = True + return changed + + +@aimm.plugins.model +class Constant(aimm.plugins.Model): + def __init__(self, **kwargs): + self.hyperparameters = {} + self._update_hp(**kwargs) + self._linear = LinearRegression() + + def fit(self, x, y, **kwargs): + if self._update_hp(**kwargs): + self._linear = LinearRegression() + + self._linear.fit(x, y) + + return self + + def predict(self, x): + try: + x = np.array(x).reshape(1, -1) + return self._linear.predict(x).reshape(-1).tolist() + except exceptions.NotFittedError: + return [] + + def serialize(self): + return pickle.dumps(self) + + @classmethod + def deserialize(cls, b): + return pickle.loads(b) + + def _update_hp(self, **kwargs): + changed = False + for key, value in kwargs.items(): + if key in self.hyperparameters: + self.hyperparameters[key] = float(value) + changed = True + return changed diff --git a/examples/0003/src_py/air_supervision/devices/air_readings.py b/examples/0003/src_py/air_supervision/devices/air_readings.py new file mode 100644 index 0000000..5229087 --- /dev/null +++ b/examples/0003/src_py/air_supervision/devices/air_readings.py @@ -0,0 +1,49 @@ +import asyncio +import logging + +import hat.aio +import hat.event.common +import hat.gateway.common +import pandas + + +mlog = logging.getLogger(__name__) + + +class AirReading(hat.gateway.common.Device): + def __init__(self, conf, event_client, event_type_prefix): + self._async_group = hat.aio.Group() + self._event_client = event_client + self._event_type_prefix = event_type_prefix + self._df = pandas.read_csv(conf["dataset_path"]) + self._async_group.spawn(self._main_loop) + + @property + def async_group(self): + return self._async_group + + async def process_events(self, events): + pass + + async def _main_loop(self): + column = "value" + for index, value in self._df[column].items(): + await asyncio.sleep(0.5) + + timestamp = self._df.iloc[index]["timestamp"] + value = (float(value) - 32) * 5 / 9 + + await self._event_client.register( + [ + hat.event.common.RegisterEvent( + type=(*self._event_type_prefix, "gateway", "reading"), + source_timestamp=hat.event.common.Timestamp(index, 0), + payload=hat.event.common.EventPayloadJson( + {"timestamp": timestamp, "value": value}, + ), + ) + ] + ) + + +info = hat.gateway.common.DeviceInfo(type="example", create=AirReading) diff --git a/examples/0003/src_py/air_supervision/modules/__init__.py b/examples/0003/src_py/air_supervision/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/0003/src_py/air_supervision/modules/controller.py b/examples/0003/src_py/air_supervision/modules/controller.py new file mode 100644 index 0000000..55569cd --- /dev/null +++ b/examples/0003/src_py/air_supervision/modules/controller.py @@ -0,0 +1,344 @@ +import abc +import csv +from datetime import datetime +from enum import Enum +from itertools import count +import numpy +from hat.event import common +from typing import Any +import hat.aio +import hat.event.common +import hat.event.server.engine +import logging + +mlog = logging.getLogger(__name__) + + +class Controller(hat.event.common.Module): + def __init__(self, conf, engine, source): + self._engine = engine + self._source = source + self._model_family = conf["model_family"] + self._batch_size = conf["batch_size"] + self._min_readings = conf["min_readings"] + self._models_conf = conf["models"] + + self._subscription = hat.event.common.create_subscription( + [ + ("user_action", self._model_family, "*"), + ("aimm", "*"), + ("gui", "system", "timeseries", "reading"), + ] + ) + + self._model_prefix = f"air_supervision.aimm.{self._model_family}" + + self._async_group = hat.aio.Group() + + self._readings = [] + self._models = {} + self._request_ids = {} + self._current_model = None + self._locked = True + + @property + def async_group(self): + return self._async_group + + @property + def subscription(self): + return self._subscription + + async def process(self, source, event): + events = [] + selector = event.type[0] + if selector == "aimm": + events = self._process_aimm(event) + elif selector == "gui": + events = self._process_reading(event) + elif selector == "user_action": + events = self._process_user_action(event) + return list(events) + + async def register_with_action_id( + self, model_type, request_id, return_type, events + ): + await self._engine.register(self._source, events) + self._request_ids[request_id] = (return_type, model_type) + + def _process_aimm(self, event): + msg_type = event.type[1] + if msg_type == "state": + yield from self._update_model_ids(event) + elif msg_type == "action": + yield from self._process_action(event) + + def _update_model_ids(self, event): + if not event.payload.data["models"] or not self._models: + return + + for model_id, model_name in event.payload.data["models"].items(): + for saved_model_name, saved_model_inst in self._models.items(): + if model_name == saved_model_name: + saved_model_inst.set_id(model_id) + + yield self._message(event.payload.data, "model_state") + + def _process_action(self, event): + payload = event.payload.data + request_id = payload["request_id"] + if request_id not in self._request_ids: + return + if payload.get("status") != "DONE": + return + + type_, model_name = self._request_ids[request_id] + + if type_ == ReturnType.CREATE: + self._current_model = model_name + self._async_group.spawn(self._models[model_name].fit) + + yield self._message(model_name, "new_current_model") + params = self._models[model_name].hyperparameters + yield self._message(params, "setting") + elif type_ == ReturnType.FIT: + self._locked = False + elif type_ == ReturnType.PREDICT: + yield from self._process_predict(event) + else: + del self._request_ids[request_id] + + def _process_predict(self, event): + values, timestamps = zip(*self._readings[: self._batch_size]) + values = [v[0] if isinstance(v, list) else v for v in values] + results = event.payload.data["result"] + + for t, r, v in zip(timestamps, results, values): + yield _register_event( + ("gui", "system", "timeseries", self._model_family), + {"timestamp": t, "result": r, "value": v}, + ) + + def _process_reading(self, event): + yield self._message(list(self._models_conf), "supported_models") + if self._locked: + return + row = self._transform_row( + event.payload.data["value"], event.payload.data["timestamp"] + ) + self._readings.append((row, event.payload.data["timestamp"])) + + if len(self._readings) != self._batch_size: + return + + if not self._readings[0][1].endswith("00:00:00"): + self._readings = self._readings[1:] + return + + model_input, _ = zip(*self._readings[: self._batch_size]) + current_model = self._models[self._current_model] + self._async_group.spawn(current_model.predict, [model_input]) + + total_readings = len(self._readings) + self._readings = self._readings[total_readings - self._min_readings :] + + def _transform_row(self, value: float, timestamp: str) -> Any: + """Convert a given value and timestamp into a table row, used to create + a table input for the AIMM model. + + value: received measurement + timestamp: time of measurement + + Returns: + Row representation""" + if self._model_family == "forecast": + return value + d = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") + return [ + float(value), + d.hour, + int((d.hour >= 7) & (d.hour <= 22)), + d.weekday(), + int(d.weekday() < 5), + ] + + def _process_user_action(self, event): + user_action = event.type[-1] + if user_action == "setting_change": + self._process_setting_change(event) + elif user_action == "model_change": + yield from self._process_model_change(event) + + def _process_setting_change(self, event): + kw = dict(event.payload.data) + del kw["action"] + current_model = self._models[self._current_model] + self._async_group.spawn(current_model.fit, **kw) + + def _process_model_change(self, event): + received_model_name = event.payload.data["model"] + + if received_model_name in self._models: + self._current_model = received_model_name + yield self._message(received_model_name, "new_current_model") + + self._current_model = received_model_name + self._locked = True + new_model = Model( + self._model_family, + self, + f"{self._model_prefix}.{received_model_name}", + self._models_conf[received_model_name], + ) + + self._models[new_model.model_class] = new_model + self._async_group.spawn(new_model.create_instance) + + def _message(self, data, type_name): + return _register_event( + ("gui", "log", self._model_family, type_name), data + ) + + +info = common.ModuleInfo(create=Controller) +request_id_counter = count(0) + + +class ReturnType(Enum): + CREATE = 1 + FIT = 2 + PREDICT = 3 + + +class Model(abc.ABC): + def __init__(self, model_family, module, model_class, hyperparameters): + self._model_family = model_family + self._module = module + + self._id = None + self._model_class = model_class + + self._hyperparameters = hyperparameters + self._executor = hat.aio.create_executor() + + @property + def model_class(self): + return self._model_class + + @property + def hyperparameters(self): + return self._hyperparameters + + def set_id(self, model_id): + self._id = model_id + + async def fit(self, **kwargs): + """Method used to invoke model fitting. + + Args: + **kwargs: matches concrete model's hyperparameters""" + if not self._id or self._model_family not in ("anomaly", "forecast"): + return + + dataset_fn = _ext_forecast_dataset + if self._model_family == "anomaly": + dataset_fn = _ext_anomaly_dataset + data = { + "args": await self._executor(dataset_fn), + "kwargs": kwargs, + "request_id": str(next(request_id_counter)), + } + await self._register_event( + ("aimm", "fit", self._id), data, ReturnType.FIT + ) + + async def create_instance(self): + event_type = ("aimm", "create_instance") + data = { + "model_type": self._model_class, + "args": [], + "kwargs": self.hyperparameters, + "request_id": str(next(request_id_counter)), + } + + await self._register_event(event_type, data, ReturnType.CREATE) + + async def predict(self, model_input): + event_type = ("aimm", "predict", self._id) + data = { + "args": model_input, + "kwargs": {}, + "request_id": str(next(request_id_counter)), + } + + await self._register_event(event_type, data, ReturnType.PREDICT) + + async def _register_event(self, event_type, data, return_type): + request_id = data["request_id"] + await self._module.register_with_action_id( + self._model_class, + request_id, + return_type, + [ + hat.event.common.RegisterEvent( + type=event_type, + source_timestamp=None, + payload=hat.event.common.EventPayloadJson(data), + ) + ], + ) + + +def _ext_line_generator(): + with open("dataset/ambient_temperature_system_failure.csv", "r") as f: + reader = csv.reader(f, delimiter="\t") + for i, line in enumerate(reader): + if not i: + continue + yield line + + +def _ext_forecast_dataset(): + values = [] + for line in _ext_line_generator(): + raw_value = float(line[0].split(",")[1]) + values.append((float(raw_value) - 32) * 5 / 9) + + x, y = [], [] + for i in range(48, len(values) - 24, 24): + x.append(values[i - 48 : i]) + y.append(values[i : i + 24]) + + x, y = numpy.array(x), numpy.array(y) + + fit_start = int(len(x) * 0.25) + return [x[fit_start:].tolist(), y[fit_start:].tolist()] + + +def _ext_anomaly_dataset(): + train_data = [] + for line in _ext_line_generator(): + timestamp = datetime.strptime( + line[0].split(",")[0], "%Y-%m-%d %H:%M:%S" + ) + value = float(line[0].split(",")[1]) + value = (float(value) - 32) * 5 / 9 + train_data.append( + [ + value, + timestamp.hour, + int((timestamp.hour >= 7) & (timestamp.hour <= 22)), + timestamp.weekday(), + int(timestamp.weekday() < 5), + ] + ) + fit_start = int(len(train_data) * 0.25) + return [train_data[fit_start:], None] + + +def _register_event(event_type, payload, source_timestamp=None): + return hat.event.common.RegisterEvent( + type=event_type, + source_timestamp=source_timestamp, + payload=hat.event.common.EventPayloadJson(data=payload), + ) diff --git a/examples/0003/src_py/air_supervision/modules/enable_all.py b/examples/0003/src_py/air_supervision/modules/enable_all.py new file mode 100644 index 0000000..ff5a9b9 --- /dev/null +++ b/examples/0003/src_py/air_supervision/modules/enable_all.py @@ -0,0 +1,37 @@ +from hat.event import common +import hat.aio +import logging + + +mlog = logging.getLogger(__name__) + + +class EnableAll(common.Module): + def __init__(self, _, engine, source): + self._source = source + self._subscription = common.create_subscription( + [("event", "?", "eventer", "gateway/gateway")] + ) + self._async_group = hat.aio.Group() + self._engine = engine + + @property + def async_group(self): + return self._async_group + + @property + def subscription(self): + return self._subscription + + async def process(self, source, event): + if event.payload.data == "CONNECTED": + return [ + common.RegisterEvent( + type=("gateway", "example", "device", "system", "enable"), + source_timestamp=None, + payload=common.EventPayloadJson(data=True), + ) + ] + + +info = common.ModuleInfo(create=EnableAll) diff --git a/examples/0003/src_py/air_supervision/modules/readings.py b/examples/0003/src_py/air_supervision/modules/readings.py new file mode 100644 index 0000000..4fbb2b0 --- /dev/null +++ b/examples/0003/src_py/air_supervision/modules/readings.py @@ -0,0 +1,32 @@ +import hat.aio +import hat.event.common + + +class ReadingsModule(hat.event.common.Module): + def __init__(self, _, engine, source): + self._source = source + self._subscription = hat.event.common.create_subscription( + [("gateway", "example", "?", "gateway", "reading")] + ) + self._async_group = hat.aio.Group() + self._engine = engine + + @property + def async_group(self): + return self._async_group + + @property + def subscription(self): + return self._subscription + + async def process(self, source, event): + return [ + hat.event.common.RegisterEvent( + type=("gui", "system", "timeseries", "reading"), + source_timestamp=event.source_timestamp, + payload=event.payload, + ) + ] + + +info = hat.event.common.ModuleInfo(create=ReadingsModule) diff --git a/examples/0003/src_scss/views/main/index.scss b/examples/0003/src_scss/views/main/index.scss new file mode 100644 index 0000000..71dbfbf --- /dev/null +++ b/examples/0003/src_scss/views/main/index.scss @@ -0,0 +1,32 @@ +html, body, input, textarea, select, button { + font-family: 'Roboto'; +} + +html, body { + font-size: 10pt; + margin: 0px; + height: 100%; +} + +#anomaly_div,#forecast_div { + width: 45%; + float: left; +} + + +.main { + height: 100%; + display: flex; + flex-direction: column; + + & > .settings { + width: 100%; + float: left; + height: 20%; + } + & > .plot { + flex-grow: 1; + width: 100%; + float: left; + } +} diff --git a/examples/0003/webpack.config.js b/examples/0003/webpack.config.js new file mode 100644 index 0000000..0f22b22 --- /dev/null +++ b/examples/0003/webpack.config.js @@ -0,0 +1,39 @@ +const path = require('path'); + +module.exports = ['login', 'main'].map(name => ({ + mode: 'none', + entry: path.resolve(__dirname, `src_js/views/${name}/index.js`), + output: { + libraryTarget: 'commonjs', + filename: 'index.js', + path: path.resolve(__dirname, `build/views/${name}`) + }, + module: { + rules: [ + { + test: /\.scss$/, + use: [ + 'style-loader', + 'css-loader', + 'resolve-url-loader', + { + 'loader': 'sass-loader', + options: { sourceMap: true } + } + ] + } + ] + }, + resolve: { + modules: [ + path.resolve(__dirname, 'src_js'), + path.resolve(__dirname, 'src_scss/views'), + path.resolve(__dirname, 'node_modules'), + ] + }, + watchOptions: { + ignored: /node_modules/ + }, + devtool: 'eval-source-map', + stats: 'errors-only' +})) diff --git a/examples/0003/yarn.lock b/examples/0003/yarn.lock new file mode 100644 index 0000000..5b4617f --- /dev/null +++ b/examples/0003/yarn.lock @@ -0,0 +1,3112 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.9.2": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz" + integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ== + dependencies: + regenerator-runtime "^0.13.4" + +"@choojs/findup@^0.2.0": + version "0.2.1" + resolved "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz" + integrity sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw== + dependencies: + commander "^2.15.1" + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@hat-core/future@^0.4.1-dev20210707": + version "0.4.1-dev20210707" + resolved "https://registry.npmjs.org/@hat-core/future/-/future-0.4.1-dev20210707.tgz" + integrity sha512-UiSq1B6Tdudr8h00lZiX2Fp4YHKK/O68Dw/u3vaK6um+BIxf1WWbS5rcB2zfOSdoSIHwCBPbZfQeVyxwBV5g6A== + +"@hat-core/juggler@^0.4.1-dev20210707": + version "0.4.1-dev20210707" + resolved "https://registry.npmjs.org/@hat-core/juggler/-/juggler-0.4.1-dev20210707.tgz" + integrity sha512-eSDCdKqcO+nqnxhJgVf3yAJK11WmoUlgo3HFcsnhzmsqo9VwLMZ7+xcs0Yhi+0mrfbIwfS40D1r4TEpMZbTjVQ== + dependencies: + "@hat-core/util" "~0.4.1-dev20210707" + jiff "*" + +"@hat-core/renderer@^0.4.1-dev20210707": + version "0.4.1-dev20210707" + resolved "https://registry.npmjs.org/@hat-core/renderer/-/renderer-0.4.1-dev20210707.tgz" + integrity sha512-d6SCD6PHpLoZ4xjFX5aFvcwsE97Huo+vZ9O30czGPx26ZXGxJ6skd0wtqe7pfbJXPn/4CDRcKlxHPD9sye7zlg== + dependencies: + "@hat-core/util" "~0.4.1-dev20210707" + snabbdom "*" + +"@hat-core/util@~0.4.1-dev20210707": + version "0.4.1-dev20210707" + resolved "https://registry.npmjs.org/@hat-core/util/-/util-0.4.1-dev20210707.tgz" + integrity sha512-WjqHvCS2B0n/fnBrBtRuz4OJiI5dGq5SxEbHlib0jllV8nwto3QJxFa2ssVIUP7DhNn4E0F6n8gQvwr9XAIS0w== + +"@mapbox/geojson-rewind@^0.5.0": + version "0.5.1" + resolved "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.1.tgz" + integrity sha512-eL7fMmfTBKjrb+VFHXCGv9Ot0zc3C0U+CwXo1IrP+EPwDczLoXv34Tgq3y+2mPSFNVUXgU42ILWJTC7145KPTA== + dependencies: + get-stream "^6.0.1" + minimist "^1.2.5" + +"@mapbox/geojson-types@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz" + integrity sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw== + +"@mapbox/jsonlint-lines-primitives@^2.0.2": + version "2.0.2" + resolved "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz" + integrity sha1-zlblOfg1UrWNENZy6k1vya3HsjQ= + +"@mapbox/mapbox-gl-supported@^1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz" + integrity sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg== + +"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": + version "0.1.0" + resolved "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz" + integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI= + +"@mapbox/tiny-sdf@^1.1.1": + version "1.2.5" + resolved "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz" + integrity sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw== + +"@mapbox/unitbezier@^0.0.0": + version "0.0.0" + resolved "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz" + integrity sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4= + +"@mapbox/vector-tile@^1.3.1": + version "1.3.1" + resolved "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz" + integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw== + dependencies: + "@mapbox/point-geometry" "~0.1.0" + +"@mapbox/whoots-js@^3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz" + integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== + +"@plotly/d3-sankey-circular@0.33.1": + version "0.33.1" + resolved "https://registry.npmjs.org/@plotly/d3-sankey-circular/-/d3-sankey-circular-0.33.1.tgz" + integrity sha512-FgBV1HEvCr3DV7RHhDsPXyryknucxtfnLwPtCKKxdolKyTFYoLX/ibEfX39iFYIL7DYbVeRtP43dbFcrHNE+KQ== + dependencies: + d3-array "^1.2.1" + d3-collection "^1.0.4" + d3-shape "^1.2.0" + elementary-circuits-directed-graph "^1.0.4" + +"@plotly/d3-sankey@0.7.2": + version "0.7.2" + resolved "https://registry.npmjs.org/@plotly/d3-sankey/-/d3-sankey-0.7.2.tgz" + integrity sha512-2jdVos1N3mMp3QW0k2q1ph7Gd6j5PY1YihBrwpkFnKqO+cqtZq3AdEYUeSGXMeLsBDQYiqTVcihYfk8vr5tqhw== + dependencies: + d3-array "1" + d3-collection "1" + d3-shape "^1.2.0" + +"@plotly/d3@3.8.0": + version "3.8.0" + resolved "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.0.tgz" + integrity sha512-L10iHgzvw3uSic/nQpYehlNzxUQvImwms5U7S95pJAEhrllzkrdQNy1Mc5DW9ab881Yr4fh300gJztKXWZDfkQ== + +"@plotly/point-cluster@^3.1.9": + version "3.1.9" + resolved "https://registry.npmjs.org/@plotly/point-cluster/-/point-cluster-3.1.9.tgz" + integrity sha512-MwaI6g9scKf68Orpr1pHZ597pYx9uP8UEFXLPbsCmuw3a84obwz6pnMXGc90VhgDNeNiLEdlmuK7CPo+5PIxXw== + dependencies: + array-bounds "^1.0.1" + binary-search-bounds "^2.0.4" + clamp "^1.0.1" + defined "^1.0.0" + dtype "^2.0.0" + flatten-vertex-data "^1.0.2" + is-obj "^1.0.1" + math-log2 "^1.0.1" + parse-rect "^1.2.0" + pick-by-alias "^1.2.0" + +"@reduxjs/toolkit@^1.8.3": + version "1.8.3" + resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz" + integrity sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg== + dependencies: + immer "^9.0.7" + redux "^4.1.2" + redux-thunk "^2.4.1" + reselect "^4.1.5" + +"@turf/area@^6.4.0": + version "6.5.0" + resolved "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz" + integrity sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/meta" "^6.5.0" + +"@turf/bbox@^6.4.0": + version "6.5.0" + resolved "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz" + integrity sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/meta" "^6.5.0" + +"@turf/centroid@^6.0.2": + version "6.5.0" + resolved "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz" + integrity sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/meta" "^6.5.0" + +"@turf/helpers@^6.5.0": + version "6.5.0" + resolved "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz" + integrity sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw== + +"@turf/meta@^6.5.0": + version "6.5.0" + resolved "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz" + integrity sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA== + dependencies: + "@turf/helpers" "^6.5.0" + +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.4.1" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz" + integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/json-schema@*", "@types/json-schema@^7.0.8": + version "7.0.11" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/node@*": + version "17.0.23" + resolved "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz" + integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz" + integrity sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg== + +"@webpack-cli/info@^1.4.1": + version "1.4.1" + resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz" + integrity sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.6.1": + version "1.6.1" + resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz" + integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abs-svg-path@^0.1.1, abs-svg-path@~0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz" + integrity sha1-32Acjo0roQ1KdtYl4japo5wnI78= + +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.4.1, acorn@^8.5.0: + version "8.7.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + +adjust-sourcemap-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz" + integrity sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +almost-equal@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/almost-equal/-/almost-equal-1.1.0.tgz" + integrity sha1-+FHGMROHV5lCdqou++jfowZszN0= + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +array-bounds@^1.0.0, array-bounds@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/array-bounds/-/array-bounds-1.0.1.tgz" + integrity sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ== + +array-find-index@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + +array-normalize@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/array-normalize/-/array-normalize-1.1.4.tgz" + integrity sha512-fCp0wKFLjvSPmCn4F5Tiw4M3lpMZoHlCjfcs7nNzuj3vqQQ1/a8cgB9DXcpDSn18c+coLnaW7rqfcYCvKbyJXg== + dependencies: + array-bounds "^1.0.0" + +array-range@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/array-range/-/array-range-1.0.1.tgz" + integrity sha1-9W5GWRhDYRxqVvd+8C7afFAIm/w= + +array-rearrange@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/array-rearrange/-/array-rearrange-2.2.2.tgz" + integrity sha512-UfobP5N12Qm4Qu4fwLDIi2v6+wZsSf6snYSxAMeKhrh37YGnNWZPRmVEKc/2wfms53TLQnzfpG8wCx2Y/6NG1w== + +atob-lite@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz" + integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +binary-search-bounds@^2.0.4: + version "2.0.5" + resolved "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz" + integrity sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA== + +bit-twiddle@^1.0.0, bit-twiddle@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz" + integrity sha1-DGwfq+KyPRcXPZpht7cJPrnhdp4= + +bitmap-sdf@^1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/bitmap-sdf/-/bitmap-sdf-1.0.3.tgz" + integrity sha512-ojYySSvWTx21cbgntR942zgEgqj38wHctN64vr4vYRFf3GKVmI23YlA94meWGkFslidwLwGCsMy2laJ3g/94Sg== + dependencies: + clamp "^1.0.1" + +bl@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz" + integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + +bootstrap@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz" + integrity sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw== + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.14.5: + version "4.20.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz" + integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== + dependencies: + caniuse-lite "^1.0.30001317" + electron-to-chromium "^1.4.84" + escalade "^3.1.1" + node-releases "^2.0.2" + picocolors "^1.0.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +caniuse-lite@^1.0.30001317: + version "1.0.30001328" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz" + integrity sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ== + +canvas-fit@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/canvas-fit/-/canvas-fit-1.5.0.tgz" + integrity sha1-rhO+Zq3kL1vg5IfjRfzjCl5bXl8= + dependencies: + element-size "^1.1.1" + +"chokidar@>=3.0.0 <4.0.0": + version "3.5.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +clamp@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz" + integrity sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ= + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +color-alpha@1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/color-alpha/-/color-alpha-1.0.4.tgz" + integrity sha512-lr8/t5NPozTSqli+duAN+x+no/2WaKTeWvxhHGN+aXT6AJ8vPlzLa7UriyjWak0pSC2jHol9JgjBYnnHsGha9A== + dependencies: + color-parse "^1.3.8" + +color-alpha@^1.0.4: + version "1.1.3" + resolved "https://registry.npmjs.org/color-alpha/-/color-alpha-1.1.3.tgz" + integrity sha512-krPYBO1RSO5LH4AGb/b6z70O1Ip2o0F0+0cVFN5FN99jfQtZFT08rQyg+9oOBNJYAn3SRwJIFC8jUEOKz7PisA== + dependencies: + color-parse "^1.4.1" + +color-id@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/color-id/-/color-id-1.1.0.tgz" + integrity sha512-2iRtAn6dC/6/G7bBIo0uupVrIne1NsQJvJxZOBCzQOfk7jRq97feaDZ3RdzuHakRXXnHGNwglto3pqtRx1sX0g== + dependencies: + clamp "^1.0.1" + +color-name@^1.0.0: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-normalize@1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/color-normalize/-/color-normalize-1.5.0.tgz" + integrity sha512-rUT/HDXMr6RFffrR53oX3HGWkDOP9goSAQGBkUaAYKjOE2JxozccdGyufageWDlInRAjm/jYPrf/Y38oa+7obw== + dependencies: + clamp "^1.0.1" + color-rgba "^2.1.1" + dtype "^2.0.0" + +color-normalize@^1.5.0: + version "1.5.2" + resolved "https://registry.npmjs.org/color-normalize/-/color-normalize-1.5.2.tgz" + integrity sha512-yYMIoyFJmUoKbCK6sBShljBWfkt8DXVfaZJn9/zvRJkF9eQJDbZhcYC6LdOVy40p4tfVwYYb9cXl8oqpu7pzBw== + dependencies: + color-rgba "^2.2.0" + dtype "^2.0.0" + +color-parse@1.3.8: + version "1.3.8" + resolved "https://registry.npmjs.org/color-parse/-/color-parse-1.3.8.tgz" + integrity sha512-1Y79qFv0n1xair3lNMTNeoFvmc3nirMVBij24zbs1f13+7fPpQClMg5b4AuKXLt3szj7BRlHMCXHplkce6XlmA== + dependencies: + color-name "^1.0.0" + defined "^1.0.0" + is-plain-obj "^1.1.0" + +color-parse@^1.3.8, color-parse@^1.4.1, color-parse@^1.4.2: + version "1.4.2" + resolved "https://registry.npmjs.org/color-parse/-/color-parse-1.4.2.tgz" + integrity sha512-RI7s49/8yqDj3fECFZjUI1Yi0z/Gq1py43oNJivAIIDSyJiOZLfYCRQEgn8HEVAj++PcRe8AnL2XF0fRJ3BTnA== + dependencies: + color-name "^1.0.0" + +color-rgba@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/color-rgba/-/color-rgba-2.1.1.tgz" + integrity sha512-VaX97wsqrMwLSOR6H7rU1Doa2zyVdmShabKrPEIFywLlHoibgD3QW9Dw6fSqM4+H/LfjprDNAUUW31qEQcGzNw== + dependencies: + clamp "^1.0.1" + color-parse "^1.3.8" + color-space "^1.14.6" + +color-rgba@^2.1.1, color-rgba@^2.2.0: + version "2.4.0" + resolved "https://registry.npmjs.org/color-rgba/-/color-rgba-2.4.0.tgz" + integrity sha512-Nti4qbzr/z2LbUWySr7H9dk3Rl7gZt7ihHAxlgT4Ho90EXWkjtkL1avTleu9yeGuqrt/chxTB6GKK8nZZ6V0+Q== + dependencies: + color-parse "^1.4.2" + color-space "^2.0.0" + +color-space@^1.14.6: + version "1.16.0" + resolved "https://registry.npmjs.org/color-space/-/color-space-1.16.0.tgz" + integrity sha512-A6WMiFzunQ8KEPFmj02OnnoUnqhmSaHaZ/0LVFcPTdlvm8+3aMJ5x1HRHy3bDHPkovkf4sS0f4wsVvwk71fKkg== + dependencies: + hsluv "^0.0.3" + mumath "^3.3.4" + +color-space@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/color-space/-/color-space-2.0.0.tgz" + integrity sha512-Bu8P/usGNuVWushjxcuaGSkhT+L2KX0cvgMGMTF0KJ7lFeqonhsntT68d6Yu3uwZzCmbF7KTB9EV67AGcUXhJw== + +colorette@^2.0.14: + version "2.0.16" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + +commander@2, commander@^2.15.1, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +compute-dims@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/compute-dims/-/compute-dims-1.1.0.tgz" + integrity sha512-YHMiIKjH/8Eom8zATk3g8/lH3HxGCZcVQyEfEoVrfWI7od/WRpTgRGShnei3jArYSx77mQqPxZNokjGHCdLfxg== + dependencies: + utils-copy "^1.0.0" + validate.io-array "^1.0.6" + validate.io-matrix-like "^1.0.2" + validate.io-ndarray-like "^1.0.0" + validate.io-positive-integer "^1.0.0" + +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +const-max-uint32@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/const-max-uint32/-/const-max-uint32-1.0.2.tgz" + integrity sha1-8Am7YjDmeO2HTdLWqc2ePL+rtnY= + +const-pinf-float64@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/const-pinf-float64/-/const-pinf-float64-1.0.0.tgz" + integrity sha1-9u+w15+cCYbT558pI6v5twtj1yY= + +convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +country-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/country-regex/-/country-regex-1.1.0.tgz" + integrity sha1-UcMz3N8Sknt+XuucEKyBEqYSCJY= + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-font-size-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz" + integrity sha1-hUh1rOmspqjS7g00WkSq6btttss= + +css-font-stretch-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz" + integrity sha1-UM7puboDH7XJUtRyMTnx4Qe1SxA= + +css-font-style-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz" + integrity sha1-XDUygT9jtKHelU0TzqhqtDM0CeQ= + +css-font-weight-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz" + integrity sha1-m8BGcayFvHJLV07106yWsNYE/Zc= + +css-font@^1.0.0, css-font@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/css-font/-/css-font-1.2.0.tgz" + integrity sha512-V4U4Wps4dPDACJ4WpgofJ2RT5Yqwe1lEH6wlOOaIxMi0gTjdIijsc5FmxQlZ7ZZyKQkkutqqvULOp07l9c7ssA== + dependencies: + css-font-size-keywords "^1.0.0" + css-font-stretch-keywords "^1.0.1" + css-font-style-keywords "^1.0.1" + css-font-weight-keywords "^1.0.0" + css-global-keywords "^1.0.1" + css-system-font-keywords "^1.0.0" + pick-by-alias "^1.2.0" + string-split-by "^1.0.0" + unquote "^1.1.0" + +css-global-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/css-global-keywords/-/css-global-keywords-1.0.1.tgz" + integrity sha1-cqmupyeW0Bmx0qMlLeTlqqN+Smk= + +css-loader@^5.2.6: + version "5.2.7" + resolved "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz" + integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== + dependencies: + icss-utils "^5.1.0" + loader-utils "^2.0.0" + postcss "^8.2.15" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^3.0.0" + semver "^7.3.5" + +css-system-font-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz" + integrity sha1-hcbwhquk6zLFcaMIav/ENLhII+0= + +csscolorparser@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz" + integrity sha1-s085HupNqPPpgjHizNjfnAQfFxs= + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +d3-array@1, d3-array@^1.2.1: + version "1.2.4" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz" + integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== + +d3-collection@1, d3-collection@^1.0.4: + version "1.0.7" + resolved "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz" + integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== + +d3-color@1: + version "1.4.1" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz" + integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q== + +d3-dispatch@1: + version "1.0.6" + resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz" + integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== + +d3-force@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz" + integrity sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg== + dependencies: + d3-collection "1" + d3-dispatch "1" + d3-quadtree "1" + d3-timer "1" + +d3-format@^1.4.5: + version "1.4.5" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz" + integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== + +d3-geo-projection@^2.9.0: + version "2.9.0" + resolved "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-2.9.0.tgz" + integrity sha512-ZULvK/zBn87of5rWAfFMc9mJOipeSo57O+BBitsKIXmU4rTVAnX1kSsJkE0R+TxY8pGNoM1nbyRRE7GYHhdOEQ== + dependencies: + commander "2" + d3-array "1" + d3-geo "^1.12.0" + resolve "^1.1.10" + +d3-geo@^1.12.0, d3-geo@^1.12.1: + version "1.12.1" + resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz" + integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg== + dependencies: + d3-array "1" + +d3-hierarchy@^1.1.9: + version "1.1.9" + resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz" + integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ== + +d3-interpolate@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz" + integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA== + dependencies: + d3-color "1" + +d3-path@1: + version "1.0.9" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +d3-quadtree@1: + version "1.0.7" + resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz" + integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA== + +d3-shape@^1.2.0: + version "1.3.7" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +d3-time-format@^2.2.3: + version "2.3.0" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz" + integrity sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ== + dependencies: + d3-time "1" + +d3-time@1, d3-time@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz" + integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== + +d3-timer@1: + version "1.0.10" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz" + integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +debug@2: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.6: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +detect-kerning@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz" + integrity sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw== + +draw-svg-path@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz" + integrity sha1-bxFtli3TFLmepTTW9Y3WbNvWk3k= + dependencies: + abs-svg-path "~0.1.1" + normalize-svg-path "~0.1.0" + +dtype@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz" + integrity sha1-zQUjI84GFETs0uj1dI9popvihDQ= + +dup@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/dup/-/dup-1.0.0.tgz" + integrity sha1-UfxaxoX4GWRp3wuQXpNLIK9bQCk= + +duplexify@^3.4.5: + version "3.7.1" + resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +earcut@^2.1.5, earcut@^2.2.2: + version "2.2.3" + resolved "https://registry.npmjs.org/earcut/-/earcut-2.2.3.tgz" + integrity sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug== + +electron-to-chromium@^1.4.84: + version "1.4.107" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.107.tgz" + integrity sha512-Huen6taaVrUrSy8o7mGStByba8PfOWWluHNxSHGBrCgEdFVLtvdQDBr9LBCF9Uci8SYxh28QNNMO0oC17wbGAg== + +element-size@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/element-size/-/element-size-1.1.1.tgz" + integrity sha1-ZOXxWdlxIWMYRby67K8nnDm1404= + +elementary-circuits-directed-graph@^1.0.4: + version "1.3.1" + resolved "https://registry.npmjs.org/elementary-circuits-directed-graph/-/elementary-circuits-directed-graph-1.3.1.tgz" + integrity sha512-ZEiB5qkn2adYmpXGnJKkxT8uJHlW/mxmBpmeqawEHzPxh9HkLD4/1mFYX5l0On+f6rcPIt8/EWlRU2Vo3fX6dQ== + dependencies: + strongly-connected-components "^1.0.1" + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +end-of-stream@^1.0.0: + version "1.4.4" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^5.9.2: + version "5.9.2" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +envinfo@^7.7.3: + version "7.8.1" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50: + version "0.10.60" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.60.tgz" + integrity sha512-jpKNXIt60htYG59/9FGf2PYT3pwMpnEbNKysU+k/4FGwyGtMotOvcZOuW+EmXXYASRqYSXQfGL5cVIthOTgbkg== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escodegen@^1.11.1: + version "1.14.3" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1, estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +ext@^1.1.2: + version "1.6.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz" + integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg== + dependencies: + type "^2.5.0" + +falafel@^2.1.0: + version "2.2.4" + resolved "https://registry.npmjs.org/falafel/-/falafel-2.2.4.tgz" + integrity sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ== + dependencies: + acorn "^7.1.1" + foreach "^2.0.5" + isarray "^2.0.1" + object-keys "^1.0.6" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-isnumeric@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/fast-isnumeric/-/fast-isnumeric-1.1.4.tgz" + integrity sha512-1mM8qOr2LYz8zGaUdmiqRDiuue00Dxjgcb1NQR7TnhLVh6sQyngP9xvLo7Sl7LZpP/sk5eb+bcyWXw530NTBZw== + dependencies: + is-string-blank "^1.0.1" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flatten-vertex-data@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz" + integrity sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw== + dependencies: + dtype "^2.0.0" + +flip-pixels@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/flip-pixels/-/flip-pixels-1.0.2.tgz" + integrity sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA== + +font-atlas@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/font-atlas/-/font-atlas-2.1.0.tgz" + integrity sha512-kP3AmvX+HJpW4w3d+PiPR2X6E1yvsBXt2yhuCw+yReO9F1WYhvZwx3c95DGZGwg9xYzDGrgJYa885xmVA+28Cg== + dependencies: + css-font "^1.0.0" + +font-measure@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/font-measure/-/font-measure-1.2.2.tgz" + integrity sha512-mRLEpdrWzKe9hbfaF3Qpr06TAjquuBVP5cHy4b3hyeNdjc9i0PO6HniGsX5vjL5OWv7+Bd++NiooNpT/s8BvIA== + dependencies: + css-font "^1.2.0" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + +from2@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +geojson-vt@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz" + integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg== + +get-canvas-context@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/get-canvas-context/-/get-canvas-context-1.0.2.tgz" + integrity sha1-1ue1C8TkyGNXzTnyJkeoS3NgHpM= + +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +gl-mat4@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.2.0.tgz" + integrity sha512-sT5C0pwB1/e9G9AvAoLsoaJtbMGjfd/jfxo8jMCKqYYEnjZuFvqV5rehqar0538EmssjdDeiEWnKyBSTw7quoA== + +gl-matrix@^3.2.1: + version "3.4.3" + resolved "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz" + integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== + +gl-text@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/gl-text/-/gl-text-1.3.1.tgz" + integrity sha512-/f5gcEMiZd+UTBJLTl3D+CkCB/0UFGTx3nflH8ZmyWcLkZhsZ1+Xx5YYkw2rgWAzgPeE35xCqBuHSoMKQVsR+w== + dependencies: + bit-twiddle "^1.0.2" + color-normalize "^1.5.0" + css-font "^1.2.0" + detect-kerning "^2.1.2" + es6-weak-map "^2.0.3" + flatten-vertex-data "^1.0.2" + font-atlas "^2.1.0" + font-measure "^1.2.2" + gl-util "^3.1.2" + is-plain-obj "^1.1.0" + object-assign "^4.1.1" + parse-rect "^1.2.0" + parse-unit "^1.0.1" + pick-by-alias "^1.2.0" + regl "^2.0.0" + to-px "^1.0.1" + typedarray-pool "^1.1.0" + +gl-util@^3.1.2: + version "3.1.3" + resolved "https://registry.npmjs.org/gl-util/-/gl-util-3.1.3.tgz" + integrity sha512-dvRTggw5MSkJnCbh74jZzSoTOGnVYK+Bt+Ckqm39CVcl6+zSsxqWk4lr5NKhkqXHL6qvZAU9h17ZF8mIskY9mA== + dependencies: + is-browser "^2.0.1" + is-firefox "^1.0.3" + is-plain-obj "^1.1.0" + number-is-integer "^1.0.1" + object-assign "^4.1.0" + pick-by-alias "^1.2.0" + weak-map "^1.0.5" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glsl-inject-defines@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz" + integrity sha1-3RqswsF/yyvT/DJBHGYz0Ne2D9Q= + dependencies: + glsl-token-inject-block "^1.0.0" + glsl-token-string "^1.0.1" + glsl-tokenizer "^2.0.2" + +glsl-resolve@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/glsl-resolve/-/glsl-resolve-0.0.1.tgz" + integrity sha1-iUvvc5ENeSyBtRQxgANdCnivdtM= + dependencies: + resolve "^0.6.1" + xtend "^2.1.2" + +glsl-token-assignments@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz" + integrity sha1-pdgqt4SZwuimuDy2lJXm5mXOAZ8= + +glsl-token-defines@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz" + integrity sha1-y4kqqVmTYjFyhHDU90AySJaX+p0= + dependencies: + glsl-tokenizer "^2.0.0" + +glsl-token-depth@^1.1.0, glsl-token-depth@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz" + integrity sha1-I8XjDuK9JViEtKKLyFC495HpXYQ= + +glsl-token-descope@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz" + integrity sha1-D8kKsyYYa4L1l7LnfcniHvzTIHY= + dependencies: + glsl-token-assignments "^2.0.0" + glsl-token-depth "^1.1.0" + glsl-token-properties "^1.0.0" + glsl-token-scope "^1.1.0" + +glsl-token-inject-block@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz" + integrity sha1-4QFfWYDBCRgkraomJfHf3ovQADQ= + +glsl-token-properties@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz" + integrity sha1-SD3D2Dnw1LXGFx0VkfJJvlPCip4= + +glsl-token-scope@^1.1.0, glsl-token-scope@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz" + integrity sha1-oXKOeN8kRE+cuT/RjvD3VQOmQ7E= + +glsl-token-string@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/glsl-token-string/-/glsl-token-string-1.0.1.tgz" + integrity sha1-WUQdL4V958NEnJRWZgIezjWOSOw= + +glsl-token-whitespace-trim@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz" + integrity sha1-RtHf6Yx1vX1QTAXX0RsbPpzJOxA= + +glsl-tokenizer@^2.0.0, glsl-tokenizer@^2.0.2: + version "2.1.5" + resolved "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz" + integrity sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA== + dependencies: + through2 "^0.6.3" + +glslify-bundle@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/glslify-bundle/-/glslify-bundle-5.1.1.tgz" + integrity sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A== + dependencies: + glsl-inject-defines "^1.0.1" + glsl-token-defines "^1.0.0" + glsl-token-depth "^1.1.1" + glsl-token-descope "^1.0.2" + glsl-token-scope "^1.1.1" + glsl-token-string "^1.0.1" + glsl-token-whitespace-trim "^1.0.0" + glsl-tokenizer "^2.0.2" + murmurhash-js "^1.0.0" + shallow-copy "0.0.1" + +glslify-deps@^1.2.5: + version "1.3.2" + resolved "https://registry.npmjs.org/glslify-deps/-/glslify-deps-1.3.2.tgz" + integrity sha512-7S7IkHWygJRjcawveXQjRXLO2FTjijPDYC7QfZyAQanY+yGLCFHYnPtsGT9bdyHiwPTw/5a1m1M9hamT2aBpag== + dependencies: + "@choojs/findup" "^0.2.0" + events "^3.2.0" + glsl-resolve "0.0.1" + glsl-tokenizer "^2.0.0" + graceful-fs "^4.1.2" + inherits "^2.0.1" + map-limit "0.0.1" + resolve "^1.0.0" + +glslify@^7.0.0, glslify@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/glslify/-/glslify-7.1.1.tgz" + integrity sha512-bud98CJ6kGZcP9Yxcsi7Iz647wuDz3oN+IZsjCRi5X1PI7t/xPKeL0mOwXJjo+CRZMqvq0CkSJiywCcY7kVYog== + dependencies: + bl "^2.2.1" + concat-stream "^1.5.2" + duplexify "^3.4.5" + falafel "^2.1.0" + from2 "^2.3.0" + glsl-resolve "0.0.1" + glsl-token-whitespace-trim "^1.0.0" + glslify-bundle "^5.0.0" + glslify-deps "^1.2.5" + minimist "^1.2.5" + resolve "^1.1.5" + stack-trace "0.0.9" + static-eval "^2.0.5" + through2 "^2.0.1" + xtend "^4.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +grid-index@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz" + integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-hover@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/has-hover/-/has-hover-1.0.1.tgz" + integrity sha1-PZdDeusZnGK4rAisvcU9O8UsF/c= + dependencies: + is-browser "^2.0.1" + +has-passive-events@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-passive-events/-/has-passive-events-1.0.0.tgz" + integrity sha512-2vSj6IeIsgvsRMyeQ0JaCX5Q3lX4zMn5HpoVc7MEhQ6pv8Iq9rsXjsp+E5ZwaT7T0xhMT0KmU8gtt1EFVdbJiw== + dependencies: + is-browser "^2.0.1" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hsluv@^0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/hsluv/-/hsluv-0.0.3.tgz" + integrity sha1-gpEH2vtKn4tSoYCe0C4JHq3mdUw= + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ieee754@^1.1.12: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +image-palette@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/image-palette/-/image-palette-2.1.0.tgz" + integrity sha512-3ImSEWD26+xuQFdP0RWR4WSXadZwvgrFhjGNpMEapTG1tf2XrBFS2dlKK5hNgH4UIaSQlSUFRn1NeA+zULIWbQ== + dependencies: + color-id "^1.1.0" + pxls "^2.0.0" + quantize "^1.0.2" + +immer@^9.0.7: + version "9.0.15" + resolved "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz" + integrity sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ== + +immutable@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz" + integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw== + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +is-base64@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/is-base64/-/is-base64-0.1.0.tgz" + integrity sha512-WRRyllsGXJM7ZN7gPTCCQ/6wNPTRDwiWdPK66l5sJzcU/oOzcIcRRf0Rux8bkpox/1yjt0F6VJRsQOIG2qz5sg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-blob@^2.0.1: + version "2.1.0" + resolved "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz" + integrity sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw== + +is-browser@^2.0.1, is-browser@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-browser/-/is-browser-2.1.0.tgz" + integrity sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ== + +is-buffer@^2.0.3: + version "2.0.5" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-finite@^1.0.1: + version "1.1.0" + resolved "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + +is-firefox@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/is-firefox/-/is-firefox-1.0.3.tgz" + integrity sha1-KioVZ3g6QX9uFYMjEI84YbCRhWI= + +is-float-array@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-float-array/-/is-float-array-1.0.0.tgz" + integrity sha512-4ew1Sx6B6kEAl3T3NOM0yB94J3NZnBdNt4paw0e8nY73yHHTeTEhyQ3Lj7EQEnv5LD+GxNTaT4L46jcKjjpLiQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-iexplorer@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-iexplorer/-/is-iexplorer-1.0.0.tgz" + integrity sha1-HXK8ZtP+Iur2Fw3ajPEJQySM/HY= + +is-mobile@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/is-mobile/-/is-mobile-2.2.2.tgz" + integrity sha512-wW/SXnYJkTjs++tVK5b6kVITZpAZPtUrt9SF80vvxGiF/Oywal+COk1jlRkiVq15RFNEQKQY31TkV24/1T5cVg== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string-blank@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz" + integrity sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw== + +is-svg-path@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/is-svg-path/-/is-svg-path-1.0.2.tgz" + integrity sha1-d6tZDBKz0gNI5cehPQBAyHeE3aA= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@^2.0.1: + version "2.0.5" + resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jiff@*: + version "0.7.3" + resolved "https://registry.npmjs.org/jiff/-/jiff-0.7.3.tgz" + integrity sha1-QttdkUDxgEOZu1AXRwValDT0D0Y= + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json5@^2.1.2: + version "2.2.1" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +kdbush@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz" + integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klona@^2.0.4: + version "2.0.5" + resolved "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz" + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^7.4.0: + version "7.8.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz" + integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg== + +map-limit@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz" + integrity sha1-63lhAxwPDo0AG/LVb6toXViCLzg= + dependencies: + once "~1.3.0" + +mapbox-gl@1.10.1: + version "1.10.1" + resolved "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.10.1.tgz" + integrity sha512-0aHt+lFUpYfvh0kMIqXqNXqoYMuhuAsMlw87TbhWrw78Tx2zfuPI0Lx31/YPUgJ+Ire0tzQ4JnuBL7acDNXmMg== + dependencies: + "@mapbox/geojson-rewind" "^0.5.0" + "@mapbox/geojson-types" "^1.0.2" + "@mapbox/jsonlint-lines-primitives" "^2.0.2" + "@mapbox/mapbox-gl-supported" "^1.5.0" + "@mapbox/point-geometry" "^0.1.0" + "@mapbox/tiny-sdf" "^1.1.1" + "@mapbox/unitbezier" "^0.0.0" + "@mapbox/vector-tile" "^1.3.1" + "@mapbox/whoots-js" "^3.1.0" + csscolorparser "~1.0.3" + earcut "^2.2.2" + geojson-vt "^3.2.1" + gl-matrix "^3.2.1" + grid-index "^1.1.0" + minimist "^1.2.5" + murmurhash-js "^1.0.0" + pbf "^3.2.1" + potpack "^1.0.1" + quickselect "^2.0.0" + rw "^1.3.3" + supercluster "^7.0.0" + tinyqueue "^2.0.3" + vt-pbf "^3.1.1" + +math-log2@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz" + integrity sha1-+4lBvl9evol55xjmJzsXjlhpRWU= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mkdirp@~0.5.0: + version "0.5.6" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mouse-change@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz" + integrity sha1-wrd+W/o0pDzhRFyBV6Tk3JiVwU8= + dependencies: + mouse-event "^1.0.0" + +mouse-event-offset@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz" + integrity sha1-39hqbiSMa6jK1TuQXVA3ogY+mYQ= + +mouse-event@^1.0.0: + version "1.0.5" + resolved "https://registry.npmjs.org/mouse-event/-/mouse-event-1.0.5.tgz" + integrity sha1-s3ie23EJmX1aky0dAdqhVDpQFzI= + +mouse-wheel@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/mouse-wheel/-/mouse-wheel-1.2.0.tgz" + integrity sha1-bSkDseqPtI5h8bU7kDZ3PwQs21w= + dependencies: + right-now "^1.0.0" + signum "^1.0.0" + to-px "^1.0.1" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mumath@^3.3.4: + version "3.3.4" + resolved "https://registry.npmjs.org/mumath/-/mumath-3.3.4.tgz" + integrity sha1-SNSg8P2MrU57Mglu6JsWGmPTC78= + dependencies: + almost-equal "^1.1.0" + +murmurhash-js@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz" + integrity sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E= + +nanoid@^3.3.1: + version "3.3.2" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz" + integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== + +native-promise-only@^0.8.1: + version "0.8.1" + resolved "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz" + integrity sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE= + +needle@^2.5.2: + version "2.9.1" + resolved "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz" + integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +node-releases@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz" + integrity sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-svg-path@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz" + integrity sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg== + dependencies: + svg-arc-to-cubic-bezier "^3.0.0" + +normalize-svg-path@~0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-0.1.0.tgz" + integrity sha1-RWNg5g7Odfvve11+FgSA5//Rb+U= + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +number-is-integer@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/number-is-integer/-/number-is-integer-1.0.1.tgz" + integrity sha1-5ZvKFy/+0nMY55x862y3LAlbIVI= + dependencies: + is-finite "^1.0.1" + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-keys@^1.0.6, object-keys@^1.0.9: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +once@~1.3.0: + version "1.3.3" + resolved "https://registry.npmjs.org/once/-/once-1.3.3.tgz" + integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parenthesis@^3.1.5: + version "3.1.8" + resolved "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz" + integrity sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw== + +parse-rect@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz" + integrity sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA== + dependencies: + pick-by-alias "^1.2.0" + +parse-svg-path@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz" + integrity sha1-en7A0esG+lMlx9PgCbhZoJtdSes= + +parse-unit@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/parse-unit/-/parse-unit-1.0.1.tgz" + integrity sha1-fhu21b7zh0wo45JSaiVBFwKR7s8= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +pbf@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz" + integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ== + dependencies: + ieee754 "^1.1.12" + resolve-protobuf-schema "^2.1.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pick-by-alias@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz" + integrity sha1-X3yysfIabh6ISgyHhVqko3NhEHs= + +picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +plotly.js@^2.7.0: + version "2.11.1" + resolved "https://registry.npmjs.org/plotly.js/-/plotly.js-2.11.1.tgz" + integrity sha512-EKAACNT9wzZdrcU79xUUgLGYCp1fi2iZAAE7lVHw0qMTB8DcdPaYUkItpUT2Q2rIyf5EPu3z3n+5AKyvDZ/azw== + dependencies: + "@plotly/d3" "3.8.0" + "@plotly/d3-sankey" "0.7.2" + "@plotly/d3-sankey-circular" "0.33.1" + "@turf/area" "^6.4.0" + "@turf/bbox" "^6.4.0" + "@turf/centroid" "^6.0.2" + canvas-fit "^1.5.0" + color-alpha "1.0.4" + color-normalize "1.5.0" + color-parse "1.3.8" + color-rgba "2.1.1" + country-regex "^1.1.0" + d3-force "^1.2.1" + d3-format "^1.4.5" + d3-geo "^1.12.1" + d3-geo-projection "^2.9.0" + d3-hierarchy "^1.1.9" + d3-interpolate "^1.4.0" + d3-time "^1.1.0" + d3-time-format "^2.2.3" + fast-isnumeric "^1.1.4" + gl-mat4 "^1.2.0" + gl-text "^1.3.1" + glslify "^7.1.1" + has-hover "^1.0.1" + has-passive-events "^1.0.0" + is-mobile "^2.2.2" + mapbox-gl "1.10.1" + mouse-change "^1.4.0" + mouse-event-offset "^3.0.2" + mouse-wheel "^1.2.0" + native-promise-only "^0.8.1" + parse-svg-path "^0.1.2" + polybooljs "^1.2.0" + probe-image-size "^7.2.3" + regl "npm:@plotly/regl@^2.1.2" + regl-error2d "^2.0.12" + regl-line2d "^3.1.2" + regl-scatter2d "^3.2.8" + regl-splom "^1.0.14" + strongly-connected-components "^1.0.1" + superscript-text "^1.0.0" + svg-path-sdf "^1.1.3" + tinycolor2 "^1.4.2" + to-px "1.0.1" + topojson-client "^3.1.0" + webgl-context "^2.2.0" + world-calendars "^1.0.3" + +plotly@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/plotly/-/plotly-1.0.6.tgz" + integrity sha1-smcsPfiDM2Jb32hJgtDj9lIdeqo= + dependencies: + mkdirp "~0.5.0" + +polybooljs@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/polybooljs/-/polybooljs-1.2.0.tgz" + integrity sha1-tDkMLgedTCYtOyUExiiNlbp6R1g= + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: + version "6.0.10" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz" + integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.1.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^7.0.35: + version "7.0.39" + resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + +postcss@^8.2.15: + version "8.4.12" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== + dependencies: + nanoid "^3.3.1" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +potpack@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz" + integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +probe-image-size@^7.2.3: + version "7.2.3" + resolved "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz" + integrity sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w== + dependencies: + lodash.merge "^4.6.2" + needle "^2.5.2" + stream-parser "~0.3.1" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +protocol-buffers-schema@^3.3.1: + version "3.6.0" + resolved "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz" + integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +pxls@^2.0.0: + version "2.3.2" + resolved "https://registry.npmjs.org/pxls/-/pxls-2.3.2.tgz" + integrity sha512-pQkwgbLqWPcuES5iEmGa10OlCf5xG0blkIF3dg7PpRZShbTYcvAdfFfGL03SMrkaSUaa/V0UpN9HWg40O2AIIw== + dependencies: + arr-flatten "^1.1.0" + compute-dims "^1.1.0" + flip-pixels "^1.0.2" + is-browser "^2.1.0" + is-buffer "^2.0.3" + to-uint8 "^1.4.1" + +quantize@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/quantize/-/quantize-1.0.2.tgz" + integrity sha1-0lrCAKd7bXD0ASfKFxoQ4zyFRt4= + +quickselect@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz" + integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +redux-thunk@^2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== + +redux@^4.1.2, redux@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz" + integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== + dependencies: + "@babel/runtime" "^7.9.2" + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + +regex-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/regex-regex/-/regex-regex-1.0.0.tgz" + integrity sha1-kEih6uuHD01IDavHb8Qs3MC8OnI= + +regl-error2d@^2.0.12: + version "2.0.12" + resolved "https://registry.npmjs.org/regl-error2d/-/regl-error2d-2.0.12.tgz" + integrity sha512-r7BUprZoPO9AbyqM5qlJesrSRkl+hZnVKWKsVp7YhOl/3RIpi4UDGASGJY0puQ96u5fBYw/OlqV24IGcgJ0McA== + dependencies: + array-bounds "^1.0.1" + color-normalize "^1.5.0" + flatten-vertex-data "^1.0.2" + object-assign "^4.1.1" + pick-by-alias "^1.2.0" + to-float32 "^1.1.0" + update-diff "^1.1.0" + +regl-line2d@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/regl-line2d/-/regl-line2d-3.1.2.tgz" + integrity sha512-nmT7WWS/WxmXAQMkgaMKWXaVmwJ65KCrjbqHGOUjjqQi6shfT96YbBOvelXwO9hG7/hjvbzjtQ2UO0L3e7YaXQ== + dependencies: + array-bounds "^1.0.1" + array-find-index "^1.0.2" + array-normalize "^1.1.4" + color-normalize "^1.5.0" + earcut "^2.1.5" + es6-weak-map "^2.0.3" + flatten-vertex-data "^1.0.2" + glslify "^7.0.0" + object-assign "^4.1.1" + parse-rect "^1.2.0" + pick-by-alias "^1.2.0" + to-float32 "^1.1.0" + +regl-scatter2d@^3.2.3, regl-scatter2d@^3.2.8: + version "3.2.8" + resolved "https://registry.npmjs.org/regl-scatter2d/-/regl-scatter2d-3.2.8.tgz" + integrity sha512-bqrqJyeHkGBa9mEfuBnRd7FUtdtZ1l+gsM2C5Ugr1U3vJG5K3mdWdVWtOAllZ5FHHyWJV/vgjVvftgFUg6CDig== + dependencies: + "@plotly/point-cluster" "^3.1.9" + array-range "^1.0.1" + array-rearrange "^2.2.2" + clamp "^1.0.1" + color-id "^1.1.0" + color-normalize "^1.5.0" + color-rgba "^2.1.1" + flatten-vertex-data "^1.0.2" + glslify "^7.0.0" + image-palette "^2.1.0" + is-iexplorer "^1.0.0" + object-assign "^4.1.1" + parse-rect "^1.2.0" + pick-by-alias "^1.2.0" + to-float32 "^1.1.0" + update-diff "^1.1.0" + +regl-splom@^1.0.14: + version "1.0.14" + resolved "https://registry.npmjs.org/regl-splom/-/regl-splom-1.0.14.tgz" + integrity sha512-OiLqjmPRYbd7kDlHC6/zDf6L8lxgDC65BhC8JirhP4ykrK4x22ZyS+BnY8EUinXKDeMgmpRwCvUmk7BK4Nweuw== + dependencies: + array-bounds "^1.0.1" + array-range "^1.0.1" + color-alpha "^1.0.4" + flatten-vertex-data "^1.0.2" + parse-rect "^1.2.0" + pick-by-alias "^1.2.0" + raf "^3.4.1" + regl-scatter2d "^3.2.3" + +regl@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/regl/-/regl-2.1.0.tgz" + integrity sha512-oWUce/aVoEvW5l2V0LK7O5KJMzUSKeiOwFuJehzpSFd43dO5spP9r+sSUfhKtsky4u6MCqWJaRL+abzExynfTg== + +"regl@npm:@plotly/regl@^2.1.2": + version "2.1.2" + resolved "https://registry.npmjs.org/@plotly/regl/-/regl-2.1.2.tgz" + integrity sha512-Mdk+vUACbQvjd0m/1JJjOOafmkp/EpmHjISsopEz5Av44CBq7rPC05HHNbYGKVyNUF2zmEoBS/TT0pd0SPFFyw== + +reselect@^4.1.5: + version "4.1.6" + resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz" + integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-protobuf-schema@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz" + integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ== + dependencies: + protocol-buffers-schema "^3.3.1" + +resolve-url-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz" + integrity sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA== + dependencies: + adjust-sourcemap-loader "^4.0.0" + convert-source-map "^1.7.0" + loader-utils "^2.0.0" + postcss "^7.0.35" + source-map "0.6.1" + +resolve@^0.6.1: + version "0.6.3" + resolved "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz" + integrity sha1-3ZV5gufnNt699TtYpN2RdUV13UY= + +resolve@^1.0.0, resolve@^1.1.10, resolve@^1.1.5, resolve@^1.9.0: + version "1.22.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +right-now@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/right-now/-/right-now-1.0.0.tgz" + integrity sha1-bolgne69fc2vja7Mmuo5z1haCRg= + +rw@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz" + integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= + +safe-buffer@^5.1.0, safe-buffer@^5.1.1: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sass-loader@11.0.1: + version "11.0.1" + resolved "https://registry.npmjs.org/sass-loader/-/sass-loader-11.0.1.tgz" + integrity sha512-Vp1LcP4slTsTNLEiDkTcm8zGN/XYYrZz2BZybQbliWA8eXveqA/AxsEjllQTpJbg2MzCsx/qNO48sHdZtOaxTw== + dependencies: + klona "^2.0.4" + neo-async "^2.6.2" + +sass@^1.34.1: + version "1.50.0" + resolved "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz" + integrity sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +semver@^7.3.5: + version "7.3.6" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz" + integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== + dependencies: + lru-cache "^7.4.0" + +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallow-copy@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz" + integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signum@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/signum/-/signum-1.0.0.tgz" + integrity sha1-dKfSvyogtA66FqkrFSEk8dVZ+nc= + +snabbdom@*: + version "3.4.0" + resolved "https://registry.npmjs.org/snabbdom/-/snabbdom-3.4.0.tgz" + integrity sha512-Sr5H1l5QxJa0B/68ZtpWi7MuGIzLWS2Up64QqXe/wzvWOjXvM9rL7+C8GhMmDVtJ7dFWypvyWJHR3nmeZN8YIQ== + +snabbdom@2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/snabbdom/-/snabbdom-2.1.0.tgz" + integrity sha512-3GPeO80A2/bob5ADrkRX8FtvW8kHbJ0aRb4XAN2MIN4bY2dprNJkhp87AgfjWgVhIkwgX9DOsuTLjWwBhMXMkQ== + +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +stack-trace@0.0.9: + version "0.0.9" + resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz" + integrity sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU= + +static-eval@^2.0.5: + version "2.1.0" + resolved "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz" + integrity sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw== + dependencies: + escodegen "^1.11.1" + +stream-parser@~0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz" + integrity sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M= + dependencies: + debug "2" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +string-split-by@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/string-split-by/-/string-split-by-1.0.0.tgz" + integrity sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A== + dependencies: + parenthesis "^3.1.5" + +string-to-arraybuffer@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/string-to-arraybuffer/-/string-to-arraybuffer-1.0.2.tgz" + integrity sha512-DaGZidzi93dwjQen5I2osxR9ERS/R7B1PFyufNMnzhj+fmlDQAc1DSDIJVJhgI8Oq221efIMbABUBdPHDRt43Q== + dependencies: + atob-lite "^2.0.0" + is-base64 "^0.1.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strongly-connected-components@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz" + integrity sha1-CSDitN9nyOrulsa2I0/inoc9upk= + +style-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz" + integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +supercluster@^7.0.0: + version "7.1.5" + resolved "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz" + integrity sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg== + dependencies: + kdbush "^3.0.0" + +superscript-text@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/superscript-text/-/superscript-text-1.0.0.tgz" + integrity sha1-58snUlZzYN9QvrBhDOjfPXHY39g= + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-arc-to-cubic-bezier@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz" + integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g== + +svg-path-bounds@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz" + integrity sha512-H4/uAgLWrppIC0kHsb2/dWUYSmb4GE5UqH06uqWBcg6LBjX2fu0A8+JrO2/FJPZiSsNOKZAhyFFgsLTdYUvSqQ== + dependencies: + abs-svg-path "^0.1.1" + is-svg-path "^1.0.1" + normalize-svg-path "^1.0.0" + parse-svg-path "^0.1.2" + +svg-path-sdf@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/svg-path-sdf/-/svg-path-sdf-1.1.3.tgz" + integrity sha512-vJJjVq/R5lSr2KLfVXVAStktfcfa1pNFjFOgyJnzZFXlO/fDZ5DmM8FpnSKKzLPfEYTVeXuVBTHF296TpxuJVg== + dependencies: + bitmap-sdf "^1.0.0" + draw-svg-path "^1.0.0" + is-svg-path "^1.0.1" + parse-svg-path "^0.1.2" + svg-path-bounds "^1.0.1" + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.1.3: + version "5.3.1" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz" + integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== + dependencies: + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + terser "^5.7.2" + +terser@^5.7.2: + version "5.12.1" + resolved "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz" + integrity sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ== + dependencies: + acorn "^8.5.0" + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.20" + +through2@^0.6.3: + version "0.6.5" + resolved "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" + integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through2@^2.0.1: + version "2.0.5" + resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +tinycolor2@^1.4.2: + version "1.4.2" + resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz" + integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== + +tinyqueue@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz" + integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== + +to-array-buffer@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/to-array-buffer/-/to-array-buffer-3.2.0.tgz" + integrity sha512-zN33mwi0gpL+7xW1ITLfJ48CEj6ZQW0ZAP0MU+2W3kEY0PAIncyuxmD4OqkUVhPAbTP7amq9j/iwvZKYS+lzSQ== + dependencies: + flatten-vertex-data "^1.0.2" + is-blob "^2.0.1" + string-to-arraybuffer "^1.0.0" + +to-float32@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/to-float32/-/to-float32-1.1.0.tgz" + integrity sha512-keDnAusn/vc+R3iEiSDw8TOF7gPiTLdK1ArvWtYbJQiVfmRg6i/CAvbKq3uIS0vWroAC7ZecN3DjQKw3aSklUg== + +to-px@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/to-px/-/to-px-1.0.1.tgz" + integrity sha1-W7rtXl1PdkRbzJA8KTojB90yRkY= + dependencies: + parse-unit "^1.0.1" + +to-px@^1.0.1: + version "1.1.0" + resolved "https://registry.npmjs.org/to-px/-/to-px-1.1.0.tgz" + integrity sha512-bfg3GLYrGoEzrGoE05TAL/Uw+H/qrf2ptr9V3W7U0lkjjyYnIfgxmVLUfhQ1hZpIQwin81uxhDjvUkDYsC0xWw== + dependencies: + parse-unit "^1.0.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-uint8@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/to-uint8/-/to-uint8-1.4.1.tgz" + integrity sha512-o+ochsMlTZyucbww8It401FC2Rx+OP2RpDeYbA6h+y9HgedDl1UjdsJ9CmzKEG7AFP9es5PmJ4eDWeeeXihESg== + dependencies: + arr-flatten "^1.1.0" + clamp "^1.0.1" + is-base64 "^0.1.0" + is-float-array "^1.0.0" + to-array-buffer "^3.0.0" + +topojson-client@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz" + integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw== + dependencies: + commander "2" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-name@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/type-name/-/type-name-2.0.2.tgz" + integrity sha1-7+fUEj2KxSr/9/QMfk3sUmYAj7Q= + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.5.0: + version "2.6.0" + resolved "https://registry.npmjs.org/type/-/type-2.6.0.tgz" + integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ== + +typedarray-pool@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/typedarray-pool/-/typedarray-pool-1.2.0.tgz" + integrity sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ== + dependencies: + bit-twiddle "^1.0.0" + dup "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +unquote@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +update-diff@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/update-diff/-/update-diff-1.1.0.tgz" + integrity sha1-9RAYLYHugZ+4LDprIrYrve2ngI8= + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utils-copy-error@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-copy-error/-/utils-copy-error-1.0.1.tgz" + integrity sha1-eR3jk8DwmJCv1Z88vqY18HmpT6U= + dependencies: + object-keys "^1.0.9" + utils-copy "^1.1.0" + +utils-copy@^1.0.0, utils-copy@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/utils-copy/-/utils-copy-1.1.1.tgz" + integrity sha1-biuXmCqozXPhGCo+b4vsPA9AWKc= + dependencies: + const-pinf-float64 "^1.0.0" + object-keys "^1.0.9" + type-name "^2.0.0" + utils-copy-error "^1.0.0" + utils-indexof "^1.0.0" + utils-regex-from-string "^1.0.0" + validate.io-array "^1.0.3" + validate.io-buffer "^1.0.1" + validate.io-nonnegative-integer "^1.0.0" + +utils-indexof@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/utils-indexof/-/utils-indexof-1.0.0.tgz" + integrity sha1-IP6r8J7xAYtSNkPoOA57yD7GG1w= + dependencies: + validate.io-array-like "^1.0.1" + validate.io-integer-primitive "^1.0.0" + +utils-regex-from-string@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/utils-regex-from-string/-/utils-regex-from-string-1.0.0.tgz" + integrity sha1-/hopCfjeD/DVGCyA+8ZU1qaH0Yk= + dependencies: + regex-regex "^1.0.0" + validate.io-string-primitive "^1.0.0" + +validate.io-array-like@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/validate.io-array-like/-/validate.io-array-like-1.0.2.tgz" + integrity sha1-evn363tRcVvrIhVmjsXM5U+t21o= + dependencies: + const-max-uint32 "^1.0.2" + validate.io-integer-primitive "^1.0.0" + +validate.io-array@^1.0.3, validate.io-array@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz" + integrity sha1-W1osr9j4uFq7L4hroVPy2Tond00= + +validate.io-buffer@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/validate.io-buffer/-/validate.io-buffer-1.0.2.tgz" + integrity sha1-hS1nNAIZFNXROvwyUxdh43IO1E4= + +validate.io-integer-primitive@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/validate.io-integer-primitive/-/validate.io-integer-primitive-1.0.0.tgz" + integrity sha1-qaoBA1X+hoHA/qbBp0rSQZyt3cY= + dependencies: + validate.io-number-primitive "^1.0.0" + +validate.io-integer@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz" + integrity sha1-FoSWSAuVviJH7EQ/IjPeT4mHgGg= + dependencies: + validate.io-number "^1.0.3" + +validate.io-matrix-like@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/validate.io-matrix-like/-/validate.io-matrix-like-1.0.2.tgz" + integrity sha1-XsMqddCInaxzbepovdYUWxVe38M= + +validate.io-ndarray-like@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/validate.io-ndarray-like/-/validate.io-ndarray-like-1.0.0.tgz" + integrity sha1-2KOw7RZbvx0vwNAHMnDPpVIpWRk= + +validate.io-nonnegative-integer@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/validate.io-nonnegative-integer/-/validate.io-nonnegative-integer-1.0.0.tgz" + integrity sha1-gGkkOgjF+Y6VQTySnf17GPP28p8= + dependencies: + validate.io-integer "^1.0.5" + +validate.io-number-primitive@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/validate.io-number-primitive/-/validate.io-number-primitive-1.0.0.tgz" + integrity sha1-0uAfICmJNp3PEVVElWQgOv5YTlU= + +validate.io-number@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz" + integrity sha1-9j/+2iSL8opnqNSODjtGGhZluvg= + +validate.io-positive-integer@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/validate.io-positive-integer/-/validate.io-positive-integer-1.0.0.tgz" + integrity sha1-ftLQO0wnVYzGagCqsPDpIYFKZYI= + dependencies: + validate.io-integer "^1.0.5" + +validate.io-string-primitive@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/validate.io-string-primitive/-/validate.io-string-primitive-1.0.1.tgz" + integrity sha1-uBNbn7E3K94C/dU60dDM1t55j+4= + +vt-pbf@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz" + integrity sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA== + dependencies: + "@mapbox/point-geometry" "0.1.0" + "@mapbox/vector-tile" "^1.3.1" + pbf "^3.2.1" + +watchpack@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz" + integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +weak-map@^1.0.5: + version "1.0.8" + resolved "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz" + integrity sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw== + +webgl-context@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/webgl-context/-/webgl-context-2.2.0.tgz" + integrity sha1-jzfXJXz23xzQpJ5qextyG5TMhqA= + dependencies: + get-canvas-context "^1.0.1" + +webpack-cli@^4.7.2: + version "4.9.2" + resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz" + integrity sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.1.1" + "@webpack-cli/info" "^1.4.1" + "@webpack-cli/serve" "^1.6.1" + colorette "^2.0.14" + commander "^7.0.0" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.8.0" + resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz" + integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.38.1: + version "5.72.0" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz" + integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.4.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.9.2" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.3.1" + webpack-sources "^3.2.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +world-calendars@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.3.tgz" + integrity sha1-slxQMrokEo/8QdCfr0pewbnBQzU= + dependencies: + object-assign "^4.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +xtend@^2.1.2: + version "2.2.0" + resolved "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz" + integrity sha1-7vax8ZjByN6vrYsXZaBNrUoBxak= diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..78e1ed6 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2584 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.0" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "babel" +version = "2.16.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "black" +version = "24.8.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "cloudpickle" +version = "3.0.0" +description = "Pickler class to extend the standard pickle.Pickler functionality" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, + {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "43.0.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[[package]] +name = "doit" +version = "0.36.0" +description = "doit - Automation Tool" +optional = false +python-versions = ">=3.8" +files = [ + {file = "doit-0.36.0-py3-none-any.whl", hash = "sha256:ebc285f6666871b5300091c26eafdff3de968a6bd60ea35dd1e3fc6f2e32479a"}, + {file = "doit-0.36.0.tar.gz", hash = "sha256:71d07ccc9514cb22fe59d98999577665eaab57e16f644d04336ae0b4bae234bc"}, +] + +[package.dependencies] +cloudpickle = "*" +importlib-metadata = ">=4.4" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "flake8" +version = "7.1.1" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" + +[[package]] +name = "flake8-pyproject" +version = "1.2.3" +description = "Flake8 plug-in loading the configuration from pyproject.toml" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a"}, +] + +[package.dependencies] +Flake8 = ">=5" + +[package.extras] +dev = ["pyTest", "pyTest-cov"] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "hat-aio" +version = "0.7.10" +description = "Hat async utility library" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_aio-0.7.10-cp310.cp311.cp312-none-any.whl", hash = "sha256:285fa4543f126012f1a4ba433bd054fa7c957d44df2fd27bcee125101ce3a6e4"}, +] + +[package.extras] +dev = ["hat-doit (>=0.15.13,<0.16.0)"] + +[[package]] +name = "hat-asn1" +version = "0.6.8" +description = "Hat ASN.1 parser and encoder" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_asn1-0.6.8-cp310.cp311.cp312-none-any.whl", hash = "sha256:8fab2adf3dbc6841fa45977a6d0fdddb4c548e45536e2dfadbbd8c335735621f"}, +] + +[package.dependencies] +hat-json = ">=0.5.28,<0.6.0" +hat-peg = ">=0.5.9,<0.6.0" +hat-util = ">=0.6.16,<0.7.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)"] + +[[package]] +name = "hat-drivers" +version = "0.8.9" +description = "Hat communication drivers" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_drivers-0.8.9-cp310.cp311.cp312-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:5c34694ec21eb44ee5dec8bfc1add955859a2e3d0739bff5542e7a140a05678d"}, + {file = "hat_drivers-0.8.9-cp310.cp311.cp312-abi3-manylinux_2_24_armv7l.whl", hash = "sha256:9e0560a52e7af792ccf9f8ace8ffbd1814ffb69ad51ddf0e0da095d54b3fb0b3"}, + {file = "hat_drivers-0.8.9-cp310.cp311.cp312-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0b459e1f78d2c2c51685c5240328fdb86d64b3115a0f9d2d461b47195a94b064"}, + {file = "hat_drivers-0.8.9-cp310.cp311.cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8fb15212d7b90e16f63757965ffa0a14d83a00f88dd7d3ba4e545817a354e3b"}, + {file = "hat_drivers-0.8.9-cp310.cp311.cp312-abi3-win_amd64.whl", hash = "sha256:386dffa9710d209f0e7d276bb68fb8ff2e42f3b0ef3c16ec4b9bd32f63585fe9"}, +] + +[package.dependencies] +aiohttp = ">=3.10.1,<3.11.0" +hat-aio = ">=0.7.10,<0.8.0" +hat-asn1 = ">=0.6.8,<0.7.0" +hat-json = ">=0.5.28,<0.6.0" +hat-sbs = ">=0.7.2,<0.8.0" +hat-util = ">=0.6.16,<0.7.0" +pyserial = ">=3.5,<4.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)", "peru (>=1.3.1)"] + +[[package]] +name = "hat-event" +version = "0.9.20" +description = "Hat event" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_event-0.9.20-cp310.cp311.cp312-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:3780173dc2f30daa79fe1b29d0dea1298168f41a63eefb24adf4301ffc209b85"}, + {file = "hat_event-0.9.20-cp310.cp311.cp312-abi3-manylinux_2_24_armv7l.whl", hash = "sha256:35b5364b0d2784bacda02d78dd5bed6be7db0efa70e07f60f45c0e96c5f6a29c"}, + {file = "hat_event-0.9.20-cp310.cp311.cp312-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:74f117ba69e8a3def76c930eed57400804ac19718daa17b8591f89051d8a75e3"}, + {file = "hat_event-0.9.20-cp310.cp311.cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d34c51e58841d2fa3b7f8389ce0c9262282d1a8d4fffa44b470ca68c0b1b9ce"}, + {file = "hat_event-0.9.20-cp310.cp311.cp312-abi3-win_amd64.whl", hash = "sha256:7a5a3659af151a8aaff68294d84b560b7f033217e73a632b11afeb78b8ee4cf2"}, +] + +[package.dependencies] +appdirs = ">=1.4.4,<1.5.0" +hat-aio = ">=0.7.10,<0.8.0" +hat-drivers = ">=0.8.8,<0.9.0" +hat-json = ">=0.5.28,<0.6.0" +hat-monitor = ">=0.8.10,<0.9.0" +hat-sbs = ">=0.7.2,<0.8.0" +hat-util = ">=0.6.16,<0.7.0" +lmdb = ">=1.4.1,<1.5.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)", "peru (>=1.3.1)", "psutil (>=5.9.5)", "sphinxcontrib-plantuml (>=0.23)", "sphinxcontrib-programoutput (>=0.17)"] + +[[package]] +name = "hat-json" +version = "0.5.28" +description = "Hat JSON library" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_json-0.5.28-cp310.cp311.cp312-none-any.whl", hash = "sha256:aaf0e5554d6786913fd431f46e9dd0e02444c804bf52f7fa90992b43b82464dc"}, +] + +[package.dependencies] +hat-util = ">=0.6.16,<0.7.0" +jsonpatch = ">=1.33,<2.0" +jsonschema = ">=4.22.0,<4.23.0" +pyyaml = ">=6.0.1,<6.1.0" +referencing = ">=0.35.1,<0.36.0" +tomli = ">=2.0.1,<2.1.0" +tomli_w = ">=1.0.0,<1.1.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)"] + +[[package]] +name = "hat-juggler" +version = "0.6.21" +description = "Hat Juggler protocol" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_juggler-0.6.21-cp310.cp311.cp312-none-any.whl", hash = "sha256:ccb42f4662a35cd57e4f670a393f50aa2ce2c2cfc044aedce7691a11fcbd078c"}, +] + +[package.dependencies] +aiohttp = ">=3.10.1,<3.11.0" +hat-aio = ">=0.7.10,<0.8.0" +hat-json = ">=0.5.28,<0.6.0" +hat-util = ">=0.6.16,<0.7.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)"] + +[[package]] +name = "hat-monitor" +version = "0.8.11" +description = "Hat monitor" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_monitor-0.8.11-cp310.cp311.cp312-none-any.whl", hash = "sha256:78d69ec3385236a7eed1309e318f8c47bb2196829d5732b2584a6198b0b79789"}, +] + +[package.dependencies] +appdirs = ">=1.4.4,<1.5.0" +hat-aio = ">=0.7.10,<0.8.0" +hat-drivers = ">=0.8.6,<0.9.0" +hat-json = ">=0.5.28,<0.6.0" +hat-juggler = ">=0.6.20,<0.7.0" +hat-sbs = ">=0.7.2,<0.8.0" +hat-util = ">=0.6.16,<0.7.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)", "psutil (>=5.9.5)", "sphinxcontrib-programoutput (>=0.17)"] + +[[package]] +name = "hat-peg" +version = "0.5.9" +description = "Hat PEG parser" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_peg-0.5.9-cp310.cp311.cp312-none-any.whl", hash = "sha256:16102d9f23178e0e3a36d74fcf9cfa688f97593b6b9e7533961ed1c5713b0029"}, +] + +[package.dependencies] +hat-util = ">=0.6.16,<0.7.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)"] + +[[package]] +name = "hat-sbs" +version = "0.7.2" +description = "Hat simple binary serializer" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_sbs-0.7.2-cp310.cp311.cp312-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:0b08e27290089dbe58fa3b210e63d46f23c9814d72aecebfcdc71d39e82201c0"}, + {file = "hat_sbs-0.7.2-cp310.cp311.cp312-abi3-manylinux_2_24_armv7l.whl", hash = "sha256:5c1a12936d8b24fffe84e15a541b7cb89aebb656ecccbf48911838d2c46af338"}, + {file = "hat_sbs-0.7.2-cp310.cp311.cp312-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:11f1e38b5f1661b520ffd5ec82c18488e679c0af7af67d6b62566388be0e18fe"}, + {file = "hat_sbs-0.7.2-cp310.cp311.cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:97a1cf578fbeef79c8c3c4fddd9c6d1738fb0d8d0eefdbb9df40437fa952666c"}, + {file = "hat_sbs-0.7.2-cp310.cp311.cp312-abi3-win_amd64.whl", hash = "sha256:c4f3a990ec55d727cc14bca8f3ffb54ca631250eabb3f18aa0092bf95ca8db25"}, +] + +[package.dependencies] +hat-json = ">=0.5.28,<0.6.0" +hat-peg = ">=0.5.9,<0.6.0" +hat-util = ">=0.6.16,<0.7.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)", "peru (>=1.3.1)"] + +[[package]] +name = "hat-syslog" +version = "0.7.18" +description = "Hat Syslog" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_syslog-0.7.18-cp310.cp311.cp312-none-any.whl", hash = "sha256:ad3b305e9edb248e7a7607a6be5628f2ccada24082fc993352cf225b3bf6cc11"}, +] + +[package.dependencies] +appdirs = ">=1.4.4,<1.5.0" +hat-aio = ">=0.7.10,<0.8.0" +hat-json = ">=0.5.28,<0.6.0" +hat-juggler = ">=0.6.19,<0.7.0" +hat-util = ">=0.6.16,<0.7.0" + +[package.extras] +dev = ["hat-doit (>=0.15.16,<0.16.0)", "sphinxcontrib-plantuml (>=0.25)", "sphinxcontrib-programoutput (>=0.17)"] + +[[package]] +name = "hat-util" +version = "0.6.16" +description = "Hat utility library" +optional = false +python-versions = ">=3.10" +files = [ + {file = "hat_util-0.6.16-cp310.cp311.cp312-none-any.whl", hash = "sha256:c2fdb3087465e42bf284db2ff14cfec7dbc1fb0e22eee5ccc5ea0c1f53d6b920"}, +] + +[package.extras] +dev = ["hat-doit (>=0.15.14,<0.16.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +description = "Useful decorators and context managers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, + {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jaraco-functools" +version = "4.0.2" +description = "Functools like those found in stdlib" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.functools-4.0.2-py3-none-any.whl", hash = "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3"}, + {file = "jaraco_functools-4.0.2.tar.gz", hash = "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["jaraco.classes", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "jsonschema" +version = "4.22.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, + {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "keyring" +version = "25.3.0" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "keyring-25.3.0-py3-none-any.whl", hash = "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae"}, + {file = "keyring-25.3.0.tar.gz", hash = "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef"}, +] + +[package.dependencies] +"jaraco.classes" = "*" +"jaraco.context" = "*" +"jaraco.functools" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab (>=1.1.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "lmdb" +version = "1.4.1" +description = "Universal Python binding for the LMDB 'Lightning' Database" +optional = false +python-versions = "*" +files = [ + {file = "lmdb-1.4.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:dcdbe27f75da9b8f58815c6ac9a1f8fa2d7a8d42abc22abb664e089002d5ffa4"}, + {file = "lmdb-1.4.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:032ce6f490caedbec642fc0a79114475e8520d1bf1e1465c6a12b8e5fe39022f"}, + {file = "lmdb-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13c5c8504d419039d6617cee24941e420d648a5b15c4b21e6491821400e5750f"}, + {file = "lmdb-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f8018a947608c4be0dc885c90f477a600be1b71285059a9c68280d36b3fb29b"}, + {file = "lmdb-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:360ac42a8772f571fdd01156e0466d6be52eea1140556a138281b7c887916ae2"}, + {file = "lmdb-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f683f3d9a1771f21a7788a9be98fae9f3ce13cb8d549d6074d0402f284572458"}, + {file = "lmdb-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73332a830c72d76d57744cd2b29eca2c258bc406273ca4ee07dc9e48ae84d712"}, + {file = "lmdb-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c178c5134e942256a830b0bca7bb052d3d7c645b4b8759d720ab49ec36b3aae"}, + {file = "lmdb-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:12047c239ab6ccbbc9db99277aabcfe1c15b1cfc9ea33b92ab30ddd6f0823a10"}, + {file = "lmdb-1.4.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:91930a2a7eb9acc4d687f9067d6f9ec83c9673bbee55823badbbee2f9a3e9970"}, + {file = "lmdb-1.4.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:1b106eb7a23b6a224bc7dfe2bd5a34c84973dda039965ae99106e10d22833dd9"}, + {file = "lmdb-1.4.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d7779ccfacd5f4c62f28485dd2427b54d19dd7016000e6237816a3750287a82"}, + {file = "lmdb-1.4.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c1f1eff7ae8d8d534309f05e274fd646dd1d4abf5157c59db59a54a55463371"}, + {file = "lmdb-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b6354df94d241e8c0158f716902224109a5f3f7ed9a24447a25f968427f61d77"}, + {file = "lmdb-1.4.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:64cf7470edfc45ff0369956e40a0784b5225097569299b91f893bd50fa336f52"}, + {file = "lmdb-1.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3c15d344731507fcfddb911a86d325e867c5574751af28591e82ecf21aad1e5"}, + {file = "lmdb-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342550b86bb6275bfb89dbde9e48385da51d57124433bd464cd7681d0702f566"}, + {file = "lmdb-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3a99a3859427fbc273ae1e932b3e7da946089757e74a05a24a19f5c4a1aba933"}, + {file = "lmdb-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f71da9bd33fd17c9cdbe2bd4ce87f4b36b8f044927df4220bec4b03f209c78a2"}, + {file = "lmdb-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e9ff50ad20d890bc63524230237a61b6eb3be96ad6a6ac475e8ba1a1f2c751f"}, + {file = "lmdb-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81abf9475a62b7ced1ac0352967106b7ed1ac5d1c1a0d23ed24abe55a28f9884"}, + {file = "lmdb-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:c9fa31743b447a3fbbbdaefc858de1c761568d855155dec54d5ad490f88856b6"}, + {file = "lmdb-1.4.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:26ef8fa7bd34a64f78f5e16fa9bcce0fe2ad682dd26ef078f95a8847dacb1171"}, + {file = "lmdb-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f5dc8a335f7925fd667d62a5e43bed3aa35959b32b233fe0112a6ef02e07877"}, + {file = "lmdb-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ba5d78b0ff130b38a56b7161ceb7e27ba4364d827d2bbb251c24b06c28c64cd"}, + {file = "lmdb-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3b84f6a349ed1bd3fa4e6c3c6b711d0389cc8d9206733cb92feffaf102998e0c"}, + {file = "lmdb-1.4.1-pp27-pypy_73-macosx_10_7_x86_64.whl", hash = "sha256:a428e6b0e298290b91b7d0ce409f595c2c9027d7f2076c39ba006290b90d14cc"}, + {file = "lmdb-1.4.1-pp27-pypy_73-win_amd64.whl", hash = "sha256:885d3f3bf51b9167d368e37b1f1277eabf595dceefd69a489bd81c1ffd3d8ffd"}, + {file = "lmdb-1.4.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4bd8e49d5209c652b2caa18a3a4c30524025d7868d34b7bb249c42f7997da240"}, + {file = "lmdb-1.4.1.tar.gz", hash = "sha256:1f4c76af24e907593487c904ef5eba1993beb38ed385af82adb25a858f2d658d"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "more-itertools" +version = "10.5.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, + {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, +] + +[[package]] +name = "multidict" +version = "6.1.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nh3" +version = "0.2.18" +description = "Python bindings to the ammonia HTML sanitization library." +optional = false +python-versions = "*" +files = [ + {file = "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86"}, + {file = "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204"}, + {file = "nh3-0.2.18-cp37-abi3-win32.whl", hash = "sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be"}, + {file = "nh3-0.2.18-cp37-abi3-win_amd64.whl", hash = "sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844"}, + {file = "nh3-0.2.18.tar.gz", hash = "sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4"}, +] + +[[package]] +name = "numpy" +version = "2.1.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, + {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, + {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, + {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, + {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, + {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, + {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, + {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, + {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, + {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pandas" +version = "2.2.2" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, + {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, + {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, + {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, + {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, + {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pkginfo" +version = "1.10.0" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097"}, + {file = "pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov", "wheel"] + +[[package]] +name = "platformdirs" +version = "4.3.3" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, + {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psutil" +version = "6.0.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +optional = false +python-versions = "*" +files = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] + +[package.extras] +cp2110 = ["hidapi"] + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-timeout" +version = "2.3.1" +description = "pytest plugin to abort hanging tests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, + {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "readme-renderer" +version = "43.0" +description = "readme_renderer is a library for rendering readme descriptions for Warehouse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "readme_renderer-43.0-py3-none-any.whl", hash = "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9"}, + {file = "readme_renderer-43.0.tar.gz", hash = "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311"}, +] + +[package.dependencies] +docutils = ">=0.13.1" +nh3 = ">=0.2.14" +Pygments = ">=2.5.1" + +[package.extras] +md = ["cmarkgfm (>=0.8.0)"] + +[[package]] +name = "referencing" +version = "0.35.1" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rfc3986" +version = "2.0.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +] + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rich" +version = "13.8.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, + {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rpds-py" +version = "0.20.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, + {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, + {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, + {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, + {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, + {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, + {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, + {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, + {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, + {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, + {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, + {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, + {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, + {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, +] + +[package.dependencies] +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" +imagesize = ">=1.3" +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] + +[[package]] +name = "sphinx-rtd-theme" +version = "2.0.0" +description = "Read the Docs theme for Sphinx" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"}, + {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"}, +] + +[package.dependencies] +docutils = "<0.21" +sphinx = ">=5,<8" +sphinxcontrib-jquery = ">=4,<5" + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-programoutput" +version = "0.17" +description = "Sphinx extension to include program output" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "sphinxcontrib-programoutput-0.17.tar.gz", hash = "sha256:300ee9b8caee8355d25cc74b4d1c7efd12e608d2ad165e3141d31e6fbc152b7f"}, + {file = "sphinxcontrib_programoutput-0.17-py2.py3-none-any.whl", hash = "sha256:0ef1c1d9159dbe7103077748214305eb4e0138e861feb71c0c346afc5fe97f84"}, +] + +[package.dependencies] +Sphinx = ">=1.7.0" + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "tenacity" +version = "9.0.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomli-w" +version = "1.0.0" +description = "A lil' TOML writer" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli_w-1.0.0-py3-none-any.whl", hash = "sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463"}, + {file = "tomli_w-1.0.0.tar.gz", hash = "sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9"}, +] + +[[package]] +name = "twine" +version = "5.1.1" +description = "Collection of utilities for publishing packages on PyPI" +optional = false +python-versions = ">=3.8" +files = [ + {file = "twine-5.1.1-py3-none-any.whl", hash = "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997"}, + {file = "twine-5.1.1.tar.gz", hash = "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db"}, +] + +[package.dependencies] +importlib-metadata = ">=3.6" +keyring = ">=15.1" +pkginfo = ">=1.8.1,<1.11" +readme-renderer = ">=35.0" +requests = ">=2.20" +requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" +rfc3986 = ">=1.4.0" +rich = ">=12.0.0" +urllib3 = ">=1.26.0" + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "yarl" +version = "1.11.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, + {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, + {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, + {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, + {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, + {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, + {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, + {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, + {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, + {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, + {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, + {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, + {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, + {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, + {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "7d2b9c4a920aa6a72ea8b75fce392bd6dc0cb24317bdb1dfbaed0cf522f11e37" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..084377a --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +create = false diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f5216e1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,64 @@ +[tool.poetry] +name = "aimm" +version = "1.2.dev0" +description = "Artificial intelligence model manager" +authors = ["zlatsic "] +license = "Apache" +readme = "README.rst" +classifiers = [ + "Development Status :: 1 - Planning", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: Unix", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", +] +include = [ + { path = "aimm/json_schema_repo.json", format = ["sdist", "wheel"] } +] + +[tool.poetry.scripts] +aimm-server = "aimm.server.main:main" + +[tool.poetry.dependencies] +python = "^3.12" +appdirs = "^1.4.4" +hat-aio = "^0.7.10" +hat-json = "^0.5.28" +hat-monitor = "^0.8.11" +hat-event = "^0.9.20" +psutil = "^6.0.0" +tenacity = "^9.0.0" + + +[tool.poetry.group.dev.dependencies] +black = "^24.8.0" +doit = "^0.36.0" +flake8 = "^7.1.1" +flake8-pyproject = "^1.2.3" +numpy = "^2.1.1" +pandas = "^2.2.2" +pytest = "^8.3.3" +pytest-asyncio = "^0.24.0" +pytest-cov = "^5.0.0" +pytest-timeout = "^2.3.1" +sphinx-rtd-theme = "^2.0.0" +sphinxcontrib-programoutput = "^0.17" +twine = "^5.1.1" +hat-syslog = "^0.7.18" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 79 + +[tool.flake8] +ignore = "E203" + +[tool.pytest.ini_options] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope="function" diff --git a/schemas_json/plugins.yaml b/schemas_json/plugins.yaml new file mode 100644 index 0000000..c9dd374 --- /dev/null +++ b/schemas_json/plugins.yaml @@ -0,0 +1,13 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://plugins.yaml#' +type: object +required: + - names +properties: + names: + type: array + items: + type: string + description: Python module name +... diff --git a/schemas_json/server/backend/event.yaml b/schemas_json/server/backend/event.yaml new file mode 100644 index 0000000..4529fa7 --- /dev/null +++ b/schemas_json/server/backend/event.yaml @@ -0,0 +1,12 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/backend/event.yaml#' +type: object +required: + - model_prefix +properties: + model_prefix: + type: array + items: + type: string +... diff --git a/schemas_json/server/backend/main.yaml b/schemas_json/server/backend/main.yaml new file mode 100644 index 0000000..0837e3f --- /dev/null +++ b/schemas_json/server/backend/main.yaml @@ -0,0 +1,10 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/backend/main.yaml#' +type: object +required: + - module +properties: + module: + type: string +... diff --git a/schemas_json/server/backend/sqlite.yaml b/schemas_json/server/backend/sqlite.yaml new file mode 100644 index 0000000..1b723a5 --- /dev/null +++ b/schemas_json/server/backend/sqlite.yaml @@ -0,0 +1,10 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/backend/sqlite.yaml#' +type: object +required: + - path +properties: + path: + type: string +... diff --git a/schemas_json/server/control/event.yaml b/schemas_json/server/control/event.yaml new file mode 100644 index 0000000..54d8240 --- /dev/null +++ b/schemas_json/server/control/event.yaml @@ -0,0 +1,47 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/control/event.yaml#' +type: object +required: + - event_prefixes + - state_event_type + - action_state_event_type +properties: + type: + const: event + event_prefixes: + type: object + properties: + create_instance: + type: array + items: + type: string + add_instance: + type: array + items: + type: string + update_instance: + type: array + items: + type: string + fit: + type: array + items: + type: string + predict: + type: array + items: + type: string + cancel: + type: array + items: + type: string + state_event_type: + type: array + items: + type: string + action_state_event_type: + type: array + items: + type: string +... diff --git a/schemas_json/server/control/main.yaml b/schemas_json/server/control/main.yaml new file mode 100644 index 0000000..a28ccd4 --- /dev/null +++ b/schemas_json/server/control/main.yaml @@ -0,0 +1,12 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/control/main.yaml#' +type: array +items: + type: object + required: + - module + properties: + module: + type: string +... diff --git a/schemas_json/server/control/repl.yaml b/schemas_json/server/control/repl.yaml new file mode 100644 index 0000000..00204b4 --- /dev/null +++ b/schemas_json/server/control/repl.yaml @@ -0,0 +1,33 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/control/repl.yaml#' +type: object +required: + - server + - users +properties: + type: + const: repl + server: + type: object + required: + - host + - port + properties: + host: + type: string + port: + type: integer + users: + type: array + items: + type: object + required: + - username + - password + properties: + username: + type: string + password: + type: string +... diff --git a/schemas_json/server/engine.yaml b/schemas_json/server/engine.yaml new file mode 100644 index 0000000..db44535 --- /dev/null +++ b/schemas_json/server/engine.yaml @@ -0,0 +1,16 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/engine.yaml#' +type: object +required: + - sigterm_timeout + - max_children + - check_children_period +properties: + sigterm_timeout: + type: number + max_children: + type: number + check_children_period: + type: number +... diff --git a/schemas_json/server/hat.yaml b/schemas_json/server/hat.yaml new file mode 100644 index 0000000..4ee3888 --- /dev/null +++ b/schemas_json/server/hat.yaml @@ -0,0 +1,41 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/hat.yaml#' +type: object +oneOf: + - required: + - monitor_component + properties: + monitor_component: + type: object + required: + - host + - port + - group + properties: + host: + type: string + default: 127.0.0.1 + port: + type: integer + default: 23010 + group: + type: string + event_server_group: + type: string + - required: + - eventer_server + properties: + eventer_server: + type: object + required: + - host + - port + properties: + host: + type: string + default: 127.0.0.1 + port: + type: integer + default: 23012 +... diff --git a/schemas_json/server/main.yaml b/schemas_json/server/main.yaml new file mode 100644 index 0000000..c7af655 --- /dev/null +++ b/schemas_json/server/main.yaml @@ -0,0 +1,27 @@ +--- +$schema: 'http://json-schema.org/schema#' +id: 'aimm://server/main.yaml#' +type: object +required: + - name + - engine + - backend + - control + - plugins + - log +properties: + name: + type: string + engine: + '$ref': 'aimm://server/engine.yaml#' + backend: + '$ref': 'aimm://server/backend/main.yaml#' + control: + '$ref': 'aimm://server/control/main.yaml#' + plugins: + '$ref': 'aimm://plugins.yaml#' + hat: + '$ref': 'aimm://server/hat.yaml#' + log: + type: object +... diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..568f964 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,14 @@ +from hat import aio +import pytest + +from aimm import plugins + + +def pytest_configure(): + aio.init_asyncio() + + +@pytest.fixture +def plugin_teardown(): + yield + plugins.unload_all() diff --git a/test/test_sys/plugins/basic.py b/test/test_sys/plugins/basic.py new file mode 100644 index 0000000..2ad965a --- /dev/null +++ b/test/test_sys/plugins/basic.py @@ -0,0 +1,29 @@ +from hat import json +import time + +from aimm import plugins + + +@plugins.model +class Model1(plugins.Model): + def __init__(self, *args, **kwargs): + self._state = {"init": {"args": args, "kwargs": kwargs}} + + def fit(self, *args, **kwargs): + self._state["fit"] = {"args": args, "kwargs": kwargs} + return self + + def predict(self, *args, **kwargs): + if isinstance(args[0], int): + time.sleep(args[0]) + return [args, kwargs] + + def serialize(self): + return json.encode(self._state).encode("utf-8") + + @classmethod + def deserialize(cls, data): + state = json.decode(data.decode("utf-8")) + m = Model1() + m._state = state + return m diff --git a/test/test_sys/test_event.py b/test/test_sys/test_event.py new file mode 100644 index 0000000..96a4123 --- /dev/null +++ b/test/test_sys/test_event.py @@ -0,0 +1,408 @@ +from hat import json +from hat import aio +from hat.drivers import tcp +import asyncio +import contextlib +import hat.event.eventer.client +import hat.event.common +import psutil +import pytest + +pytestmark = pytest.mark.asyncio + + +@pytest.fixture +def conf_path(tmp_path): + return tmp_path + + +@pytest.fixture +def event_port(unused_tcp_port_factory): + return unused_tcp_port_factory() + + +@pytest.fixture +async def event_server(event_port, conf_path): + conf = { + "type": "event", + "log": { + "disable_existing_loggers": False, + "formatters": {"default": {}}, + "handlers": { + "syslog": { + "class": "hat.syslog.handler.SysLogHandler", + "comm_type": "TCP", + "formatter": "default", + "host": "127.0.0.1", + "level": "INFO", + "port": 6514, + "queue_size": 10, + } + }, + "root": { + "handlers": ["syslog"], + "level": "INFO", + }, + "version": 1, + }, + "name": "event-server", + "server_id": 0, + "backend": {"module": "hat.event.backends.dummy"}, + "modules": [], + "eventer_server": {"host": "127.0.0.1", "port": event_port}, + "synced_restart_engine": False, + } + event_conf_path = conf_path / "event.yaml" + json.encode_file(conf, event_conf_path) + proc = psutil.Popen( + ["python", "-m", "hat.event.server", "--conf", str(event_conf_path)] + ) + try: + while not _listens_on(proc, event_port): + await asyncio.sleep(0.1) + yield proc + finally: + proc.kill() + proc.wait() + + +def simple_conf(event_port): + return { + "log": { + "version": 1, + "formatters": {"default": {}}, + "handlers": { + "syslog": { + "class": "hat.syslog.handler.SysLogHandler", + "host": "127.0.0.1", + "port": 6514, + "comm_type": "TCP", + "level": "INFO", + "formatter": "default", + "queue_size": 10, + } + }, + "root": {"level": "INFO", "handlers": ["syslog"]}, + "disable_existing_loggers": False, + }, + "engine": { + "sigterm_timeout": 5, + "max_children": 5, + "check_children_period": 3, + }, + "backend": { + "module": "aimm.server.backend.event", + "model_prefix": ["aimm", "model"], + }, + "control": [ + { + "module": "aimm.server.control.event", + "event_prefixes": { + "create_instance": ["create_instance"], + "add_instance": ["add_instance"], + "update_instance": ["update_instance"], + "fit": ["fit"], + "predict": ["predict"], + "cancel": ["cancel"], + }, + "state_event_type": ["aimm", "state"], + "action_state_event_type": ["aimm", "action_state"], + } + ], + "plugins": {"names": ["test_sys.plugins.basic"]}, + "hat": {"eventer_server": {"host": "127.0.0.1", "port": event_port}}, + "name": "sys-test-event", + } + + +@pytest.fixture +async def aimm_server_proc(event_port, event_server, conf_path): + conf = simple_conf(event_port) + aimm_conf_path = conf_path / "aimm.yaml" + json.encode_file(conf, aimm_conf_path) + proc = psutil.Popen( + ["python", "-m", "aimm.server", "--conf", str(aimm_conf_path)] + ) + await asyncio.sleep(1) + yield proc + proc.kill() + proc.wait() + + +@pytest.fixture +def event_client_factory(event_port): + @contextlib.asynccontextmanager + async def factory(subscriptions): + queue = aio.Queue() + + async def events_cb(_, events): + queue.put_nowait(events) + + client = await hat.event.eventer.client.connect( + tcp.Address(host="127.0.0.1", port=event_port), + "sys-test-client", + subscriptions=subscriptions, + events_cb=events_cb, + ) + yield client, queue + await client.async_close() + + return factory + + +async def test_create_instance(aimm_server_proc, event_client_factory): + model_type = "test_sys.plugins.basic.Model1" + + async with event_client_factory( + [("aimm", "action_state"), ("aimm", "model", "*")] + ) as (client, events_queue): + args = ["a1", "a2"] + kwargs = {"k1": "1", "k2": "2"} + await client.register( + [ + _register_event( + ("create_instance",), + { + "model_type": model_type, + "args": args, + "kwargs": kwargs, + "request_id": 1, + }, + ) + ], + with_response=True, + ) + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "IN_PROGRESS" + assert payload["result"] is None + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "model", "1") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"type", "instance"} + assert payload["type"] == model_type + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "DONE" + assert payload["result"] == 1 + + +async def _create_instance(client, model_type, events_queue): + args = ["a1", "a2"] + kwargs = {"k1": "1", "k2": "2"} + await client.register( + [ + _register_event( + ("create_instance",), + { + "model_type": model_type, + "args": args, + "kwargs": kwargs, + "request_id": 1, + }, + ) + ], + with_response=True, + ) + + events = await events_queue.get() + event = events[0] + assert event.payload.data["status"] == "IN_PROGRESS" + + events = await events_queue.get() + event = events[0] + assert event.type == ("aimm", "model", "1") + + events = await events_queue.get() + event = events[0] + payload = event.payload.data + assert payload["status"] == "DONE" + model_id = payload["result"] + + return model_id + + +async def test_fit(aimm_server_proc, event_client_factory): + model_type = "test_sys.plugins.basic.Model1" + + async with event_client_factory( + [("aimm", "action_state"), ("aimm", "model", "*")] + ) as (client, events_queue): + model_id = await _create_instance(client, model_type, events_queue) + + args = ["a3", "a4"] + kwargs = {"k3": "3", "k4": "4"} + await client.register( + [ + _register_event( + ("fit", str(model_id)), + {"args": args, "kwargs": kwargs, "request_id": 1}, + ) + ], + with_response=True, + ) + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "IN_PROGRESS" + assert payload["result"] is None + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "model", str(model_id)) + assert event.source_timestamp is None + assert event.payload.data["type"] == model_type + assert event.payload.data["instance"] is not None + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "DONE" + assert payload["result"] is None + + +async def test_predict(aimm_server_proc, event_client_factory): + model_type = "test_sys.plugins.basic.Model1" + + async with event_client_factory( + [("aimm", "action_state"), ("aimm", "model", "*")] + ) as (client, events_queue): + model_id = await _create_instance(client, model_type, events_queue) + + args = ["a3", "a4"] + kwargs = {"k3": "3", "k4": "4"} + await client.register( + [ + _register_event( + ("predict", str(model_id)), + {"args": args, "kwargs": kwargs, "request_id": 1}, + ) + ], + with_response=True, + ) + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "IN_PROGRESS" + assert payload["result"] is None + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "model", str(model_id)) + assert event.source_timestamp is None + assert event.payload.data["type"] == model_type + assert event.payload.data["instance"] is not None + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "DONE" + assert payload["result"] == [args, kwargs] + + +async def test_cancel(aimm_server_proc, event_client_factory): + model_type = "test_sys.plugins.basic.Model1" + + async with event_client_factory( + [("aimm", "action_state"), ("aimm", "model", "*")] + ) as (client, events_queue): + model_id = await _create_instance(client, model_type, events_queue) + await client.register( + [ + _register_event( + ("predict", str(model_id)), + { + "args": [10], + "kwargs": {}, + "request_id": "1", + }, # sleep 10 seconds + ) + ], + with_response=True, + ) + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "IN_PROGRESS" + assert payload["result"] is None + + event_queue = aio.Queue() + + async def wait_events(): + event_queue.put_nowait(await events_queue.get()) + + async with aio.Group() as group: + group.spawn(wait_events) + await asyncio.sleep(2) + assert event_queue.empty() + + await client.register( + [_register_event(("cancel",), "1")], with_response=True + ) + + events = await events_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("aimm", "action_state") + assert event.source_timestamp is None + payload = event.payload.data + assert set(payload.keys()) == {"request_id", "status", "result"} + assert payload["status"] == "CANCELLED" + assert payload["result"] is None + + +def _listens_on(proc, port): + return port in ( + conn.laddr.port + for conn in proc.net_connections() + if conn.status == "LISTEN" + ) + + +def _register_event(event_type, payload): + return hat.event.common.RegisterEvent( + type=event_type, + source_timestamp=None, + payload=hat.event.common.EventPayloadJson(payload), + ) diff --git a/test/test_sys/test_repl.py b/test/test_sys/test_repl.py new file mode 100644 index 0000000..f9ac653 --- /dev/null +++ b/test/test_sys/test_repl.py @@ -0,0 +1,101 @@ +from hat import json +import asyncio +import hashlib +import pytest + +import aimm.client.repl + +pytestmark = pytest.mark.asyncio + + +@pytest.fixture +def data_path(tmp_path): + return tmp_path + + +@pytest.fixture +def aimm_port(unused_tcp_port): + return unused_tcp_port + + +def simple_conf(aimm_port, backend_path): + password_hash = hashlib.sha256() + password_hash.update("pass".encode("utf-8")) + return { + "log": { + "version": 1, + "formatters": {"default": {}}, + "handlers": { + "syslog": { + "class": "hat.syslog.handler.SysLogHandler", + "host": "127.0.0.1", + "port": 6514, + "comm_type": "TCP", + "level": "INFO", + "formatter": "default", + "queue_size": 10, + } + }, + "root": {"level": "INFO", "handlers": ["syslog"]}, + "disable_existing_loggers": False, + }, + "engine": { + "sigterm_timeout": 5, + "max_children": 5, + "check_children_period": 3, + }, + "backend": { + "module": "aimm.server.backend.sqlite", + "path": str(backend_path), + }, + "control": [ + { + "module": "aimm.server.control.repl", + "server": {"host": "127.0.0.1", "port": aimm_port}, + "users": [ + {"username": "user", "password": password_hash.hexdigest()} + ], + } + ], + "plugins": {"names": ["test_sys.plugins.basic"]}, + "name": "sys-test-repl", + } + + +@pytest.fixture +async def aimm_server_proc(data_path, aimm_port): + conf = simple_conf(aimm_port, data_path / "aimm.db") + aimm_conf_path = data_path / "aimm.yaml" + json.encode_file(conf, aimm_conf_path) + proc = await asyncio.create_subprocess_shell( + f"python -m aimm.server --conf {aimm_conf_path}" + ) + await asyncio.sleep(1) + yield proc + proc.kill() + await proc.wait() + + +@pytest.fixture +async def repl_client(monkeypatch, aimm_server_proc, aimm_port): + client = aimm.client.repl.AIMM() + with monkeypatch.context() as ctx: + aimm.client.repl.input = input + ctx.setattr(aimm.client.repl, "input", lambda _: "user") + ctx.setattr(aimm.client.repl, "getpass", lambda _: "pass") + await client.connect(f"ws://127.0.0.1:{aimm_port}") + yield client + await client.async_close() + + +async def test_connects(repl_client): + assert repl_client + + +async def test_workflow(repl_client): + model = await repl_client.create_instance( + "test_sys.plugins.basic.Model1", "a1", "a2", k1="1", k2="2" + ) + await model.fit("a1", "a2", k1="1", k2="2") + prediction = await model.predict(0.1, a="b", c="d") + assert prediction == [[0.1], dict(a="b", c="d")] diff --git a/test/test_sys/test_runners.py b/test/test_sys/test_runners.py new file mode 100644 index 0000000..33324db --- /dev/null +++ b/test/test_sys/test_runners.py @@ -0,0 +1,295 @@ +from hat import json +from hat import aio +from hat.drivers import tcp +import asyncio +import contextlib +import hat.event.eventer.client +import hat.event.common +import psutil +import pytest + +pytestmark = pytest.mark.asyncio + + +@pytest.fixture +def conf_path(tmp_path): + return tmp_path + + +@pytest.fixture +def monitor_port(unused_tcp_port_factory): + return unused_tcp_port_factory() + + +@pytest.fixture +async def monitor_server(monitor_port, unused_tcp_port_factory, conf_path): + master_port = unused_tcp_port_factory() + ui_port = unused_tcp_port_factory() + conf = { + "type": "monitor", + "log": { + "version": 1, + "formatters": {"default": {}}, + "handlers": { + "syslog": { + "class": "hat.syslog.handler.SysLogHandler", + "host": "127.0.0.1", + "port": 6514, + "comm_type": "TCP", + "level": "INFO", + "formatter": "default", + "queue_size": 10, + } + }, + "root": {"level": "INFO", "handlers": ["syslog"]}, + "disable_existing_loggers": False, + }, + "default_algorithm": "BLESS_ALL", + "group_algorithms": {}, + "server": { + "host": "127.0.0.1", + "port": monitor_port, + "default_rank": 1, + }, + "master": {"host": "127.0.0.1", "port": master_port}, + "slave": { + "parents": [], + "connect_timeout": 5, + "connect_retry_count": 5, + "connect_retry_delay": 5, + }, + "ui": {"host": "127.0.0.1", "port": ui_port}, + } + monitor_conf_path = conf_path / "monitor.yaml" + json.encode_file(conf, monitor_conf_path) + proc = psutil.Popen( + [ + "python", + "-m", + "hat.monitor.server", + "--conf", + str(monitor_conf_path), + ] + ) + try: + while not _listens_on(proc, monitor_port): + await asyncio.sleep(0.1) + yield proc + finally: + proc.kill() + proc.wait() + + +@pytest.fixture +def event_port(unused_tcp_port_factory): + return unused_tcp_port_factory() + + +@pytest.fixture +def event_server_factory(event_port, conf_path): + @contextlib.asynccontextmanager + async def factory(monitor_port=None): + conf = { + "type": "event", + "log": { + "disable_existing_loggers": False, + "formatters": {"default": {}}, + "handlers": { + "syslog": { + "class": "hat.syslog.handler.SysLogHandler", + "comm_type": "TCP", + "formatter": "default", + "host": "127.0.0.1", + "level": "INFO", + "port": 6514, + "queue_size": 10, + } + }, + "root": { + "handlers": ["syslog"], + "level": "INFO", + }, + "version": 1, + }, + "name": "event-server", + "server_id": 0, + "backend": {"module": "hat.event.backends.dummy"}, + "modules": [], + "eventer_server": {"host": "127.0.0.1", "port": event_port}, + "synced_restart_engine": False, + } + if monitor_port: + conf["monitor_component"] = { + "group": "event", + "host": "127.0.0.1", + "port": monitor_port, + } + event_conf_path = conf_path / "event.yaml" + json.encode_file(conf, event_conf_path) + proc = psutil.Popen( + [ + "python", + "-m", + "hat.event.server", + "--conf", + str(event_conf_path), + ] + ) + try: + while not _listens_on(proc, event_port): + await asyncio.sleep(0.1) + yield proc + finally: + proc.kill() + proc.wait() + + return factory + + +def aimm_conf(hat_conf): + conf = { + "log": { + "version": 1, + "formatters": {"default": {}}, + "handlers": { + "syslog": { + "class": "hat.syslog.handler.SysLogHandler", + "host": "127.0.0.1", + "port": 6514, + "comm_type": "TCP", + "level": "INFO", + "formatter": "default", + "queue_size": 10, + } + }, + "root": {"level": "INFO", "handlers": ["syslog"]}, + "disable_existing_loggers": False, + }, + "engine": { + "sigterm_timeout": 5, + "max_children": 5, + "check_children_period": 3, + }, + "backend": { + "module": "aimm.server.backend.dummy", + }, + "control": [], + "plugins": {"names": ["test_sys.plugins.basic"]}, + "name": "sys-test-event", + } + if hat_conf is not None: + conf["hat"] = hat_conf + return conf + + +@pytest.fixture +def aimm_server_factory(conf_path): + @contextlib.asynccontextmanager + async def factory(hat_conf): + conf = aimm_conf(hat_conf) + aimm_conf_path = conf_path / "aimm.yaml" + json.encode_file(conf, aimm_conf_path) + proc = psutil.Popen( + ["python", "-m", "aimm.server", "--conf", str(aimm_conf_path)] + ) + await asyncio.sleep(1) + yield proc + proc.kill() + proc.wait() + + return factory + + +@pytest.fixture +def event_client_factory(event_port): + @contextlib.asynccontextmanager + async def factory(subscriptions): + queue = aio.Queue() + + async def events_cb(_, events): + queue.put_nowait(events) + + client = await hat.event.eventer.client.connect( + tcp.Address(host="127.0.0.1", port=event_port), + "sys-test-client", + subscriptions=subscriptions, + events_cb=events_cb, + ) + yield client, queue + await client.async_close() + + return factory + + +async def test_single(aimm_server_factory): + async with aimm_server_factory(None) as aimm_proc: + assert aimm_proc.is_running() + + +async def test_monitor_only(aimm_server_factory, monitor_port, monitor_server): + async with aimm_server_factory( + { + "monitor_component": { + "host": "127.0.0.1", + "port": monitor_port, + "group": "aimm", + } + } + ) as aimm_proc: + assert aimm_proc.is_running() + assert _connected_to(aimm_proc, monitor_port) + + +async def test_eventer_only( + aimm_server_factory, event_port, event_server_factory +): + async with event_server_factory(): + async with aimm_server_factory( + { + "eventer_server": { + "host": "127.0.0.1", + "port": event_port, + } + } + ) as aimm_proc: + assert aimm_proc.is_running() + assert _connected_to(aimm_proc, event_port) + + +async def test_complete_hat( + aimm_server_factory, + event_port, + event_server_factory, + monitor_port, + monitor_server, +): + async with event_server_factory(monitor_port): + async with aimm_server_factory( + { + "monitor_component": { + "host": "127.0.0.1", + "port": monitor_port, + "group": "aimm", + "event_server_group": "event", + } + } + ) as aimm_proc: + assert aimm_proc.is_running() + assert _connected_to(aimm_proc, monitor_port) + assert _connected_to(aimm_proc, event_port) + + +def _listens_on(proc, port): + return port in ( + conn.laddr.port + for conn in proc.net_connections() + if conn.status == "LISTEN" + ) + + +def _connected_to(proc, port): + return port in ( + conn.raddr.port + for conn in proc.net_connections() + if conn.status == "ESTABLISHED" + ) diff --git a/test/test_unit/test_plugins.py b/test/test_unit/test_plugins.py new file mode 100644 index 0000000..81877a4 --- /dev/null +++ b/test/test_unit/test_plugins.py @@ -0,0 +1,100 @@ +from aimm import plugins + + +def dummy_state_cb(state): + print("state:", state) + + +def test_instantiate(plugin_teardown): + @plugins.instantiate("test", state_cb_arg_name="state_cb") + def instantiate(state_cb): + return state_cb + + assert plugins.exec_instantiate("test", dummy_state_cb) == dummy_state_cb + + +def test_data_access(plugin_teardown): + @plugins.data_access("test", state_cb_arg_name="state_cb") + def data_access(state_cb): + return state_cb + + assert plugins.exec_data_access("test", dummy_state_cb) == dummy_state_cb + + +def test_fit(plugin_teardown): + @plugins.fit( + ["test"], state_cb_arg_name="state_cb", instance_arg_name="instance" + ) + def fit(state_cb, instance): + return state_cb, instance + + result = plugins.exec_fit("test", "instance", dummy_state_cb) + assert result == (dummy_state_cb, "instance") + + +def test_predict(plugin_teardown): + @plugins.predict( + ["test"], state_cb_arg_name="state_cb", instance_arg_name="instance" + ) + def predict(state_cb, instance): + return state_cb, instance + + assert plugins.exec_predict("test", "instance", dummy_state_cb) == ( + "instance", + (dummy_state_cb, "instance"), + ) + + +def test_serialize(plugin_teardown): + @plugins.serialize(["test"]) + def serialize(instance): + return instance + + assert plugins.exec_serialize("test", "instance") == "instance" + + +def test_deserialize(plugin_teardown): + @plugins.deserialize(["test"]) + def deserialize(i_bytes): + return i_bytes + + instance_bytes = "instance bytes".encode("utf-8") + + assert plugins.exec_deserialize("test", instance_bytes) == instance_bytes + + +def test_model(plugin_teardown): + @plugins.model + class Model1(plugins.Model): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + self.fit_args = [] + self.fit_kwargs = {} + + def fit(self, *args, **kwargs): + self.fit_args = args + self.fit_kwargs = kwargs + return self + + def predict(self, *args, **kwargs): + return args, kwargs + + def serialize(self): + return bytes() + + @classmethod + def deserialize(cls, _): + return Model1() + + model_type = "test_plugins.Model1" + + model = plugins.exec_instantiate( + model_type, dummy_state_cb, "a1", "a2", k1="1", k2="2" + ) + assert model.args == ("a1", "a2") + assert model.kwargs == {"k1": "1", "k2": "2"} + + plugins.exec_fit(model_type, model, dummy_state_cb, "fit_a1", fit_k1="1") + assert model.fit_args == ("fit_a1",) + assert model.fit_kwargs == {"fit_k1": "1"} diff --git a/test/test_unit/test_server/test_backend/__init__.py b/test/test_unit/test_server/test_backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_unit/test_server/test_backend/test_event.py b/test/test_unit/test_server/test_backend/test_event.py new file mode 100644 index 0000000..8bc03b2 --- /dev/null +++ b/test/test_unit/test_server/test_backend/test_event.py @@ -0,0 +1,102 @@ +import hat.event.common +from hat import aio +import base64 +import pytest + +from aimm.server.backend import event +from aimm.server import common +from aimm import plugins + + +class MockClient: + def __init__(self, query_result=None): + self._query_result = query_result + if query_result is None: + self._query_result = hat.event.common.QueryResult( + events=[], more_follows=False + ) + self._query_queue = aio.Queue() + self._register_queue = aio.Queue() + self._receive_queue = aio.Queue() + + async def query(self, query_data): + self._query_queue.put_nowait(query_data) + return self._query_result + + async def register(self, events, _=False): + self._register_queue.put_nowait(events) + return events + + +@pytest.fixture +def string_plugins(plugin_teardown): + @plugins.serialize(["type"]) + def serialize_type(instance): + return instance.encode("utf-8") + + @plugins.deserialize(["type"]) + def deserialize_type(instance_bytes): + return instance_bytes.decode("utf-8") + + +async def test_create_model(string_plugins): + mock_client = MockClient() + backend = await event.create({"model_prefix": ["model"]}, mock_client) + assert await backend.get_models() == [] + + await backend.create_model("type", "instance") + events = await mock_client._register_queue.get() + assert len(events) == 1 + ev = events[0] + assert ev.type == ("model", "1") + assert ev.source_timestamp is None + exp_instance_bytes = "instance".encode("utf-8") + assert ev.payload.data == { + "type": "type", + "instance": base64.b64encode(exp_instance_bytes).decode("utf-8"), + } + await backend.async_close() + + +async def test_get_models(string_plugins): + mock_client = MockClient() + backend = await event.create({"model_prefix": ["model"]}, mock_client) + assert await backend.get_models() == [] + + await backend.create_model("type", "instance") + events = await mock_client._register_queue.get() + mock_client._query_result = hat.event.common.QueryResult( + events=events, more_follows=False + ) + + models = await backend.get_models() + assert len(models) == 1 + model = models[0] + assert model.instance == "instance" + assert model.model_type == "type" + assert model.instance_id == 1 + await backend.async_close() + + +async def test_update_model(string_plugins): + mock_client = MockClient() + backend = await event.create({"model_prefix": ["model"]}, mock_client) + assert await backend.get_models() == [] + + await backend.create_model("type", "instance") + await mock_client._register_queue.get() + + await backend.update_model( + common.Model(instance="instance2", model_type="type", instance_id=1) + ) + events = await mock_client._register_queue.get() + assert len(events) == 1 + ev = events[0] + assert ev.type == ("model", "1") + assert ev.source_timestamp is None + exp_instance_bytes = "instance2".encode("utf-8") + assert ev.payload.data == { + "type": "type", + "instance": base64.b64encode(exp_instance_bytes).decode("utf-8"), + } + await backend.async_close() diff --git a/test/test_unit/test_server/test_backend/test_sqlite.py b/test/test_unit/test_server/test_backend/test_sqlite.py new file mode 100644 index 0000000..f20c665 --- /dev/null +++ b/test/test_unit/test_server/test_backend/test_sqlite.py @@ -0,0 +1,40 @@ +import pytest + +from aimm.server.backend import sqlite +from aimm.server import common +from aimm import plugins + + +@pytest.fixture +async def backend(tmp_path): + backend = await sqlite.create({"path": str(tmp_path / "backend.db")}, None) + yield backend + await backend.async_close() + + +async def test_create(tmp_path): + backend_object = await sqlite.create( + {"path": str(tmp_path / "backend.db")}, None + ) + assert backend_object + await backend_object.async_close() + + +async def test_models(backend, plugin_teardown): + @plugins.serialize(["test"]) + def serialize(instance): + return instance.encode("utf-8") + + @plugins.deserialize(["test"]) + def deserialize(instance_blob): + return instance_blob.decode("utf-8") + + await backend.create_model("test", "instance") + expected_model = common.Model( + instance_id=1, instance="instance", model_type="test" + ) + assert await backend.get_models() == [expected_model] + + model_updated = expected_model._replace(instance="instance2") + await backend.update_model(model_updated) + assert await backend.get_models() == [model_updated] diff --git a/test/test_unit/test_server/test_control/__init__.py b/test/test_unit/test_server/test_control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_unit/test_server/test_control/test_event.py b/test/test_unit/test_server/test_control/test_event.py new file mode 100644 index 0000000..09c4d4b --- /dev/null +++ b/test/test_unit/test_server/test_control/test_event.py @@ -0,0 +1,507 @@ +from hat import aio +import asyncio +import base64 +import hat.event.common +import pytest + +import aimm.server.control.event +import aimm.server.engine +from aimm.server import common +from aimm import plugins + + +class MockClient: + def __init__(self): + self._register_queue = aio.Queue() + self._receive_queue = aio.Queue() + + async def register(self, events, _=False): + self._register_queue.put_nowait(events) + + +class MockEngine(common.Engine): + def __init__( + self, + state=None, + create_instance_cb=None, + add_instance_cb=None, + update_instance_cb=None, + fit_cb=None, + predict_cb=None, + ): + if state is None: + state = {"models": {}, "actions": {}} + self._state = state + self._cb = None + self._create_instance_cb = create_instance_cb + self._add_instance_cb = add_instance_cb + self._update_instance_cb = update_instance_cb + self._fit_cb = fit_cb + self._predict_cb = predict_cb + + self._group = aio.Group() + + @property + def async_group(self): + return self._group + + @property + def state(self): + return self._state + + @state.setter + def state(self, value): + self._state = value + self._cb() + + def subscribe_to_state_change(self, cb): + self._cb = cb + + def create_instance(self, *args, **kwargs): + if self._create_instance_cb: + return aimm.server.engine.create_action( + self._group.create_subgroup(), + aio.call, + self._create_instance_cb, + *args, + **kwargs + ) + raise NotImplementedError() + + async def add_instance(self, *args, **kwargs): + if self._add_instance_cb: + return await aio.call(self._add_instance_cb, *args, **kwargs) + raise NotImplementedError() + + async def update_instance(self, *args, **kwargs): + if self._update_instance_cb: + return await aio.call(self._update_instance_cb, *args, **kwargs) + raise NotImplementedError() + + def fit(self, *args, **kwargs): + if self._fit_cb: + return aimm.server.engine.create_action( + self._group.create_subgroup(), + aio.call, + self._fit_cb, + *args, + **kwargs + ) + raise NotImplementedError() + + def predict(self, *args, **kwargs): + if self._predict_cb: + return aimm.server.engine.create_action( + self._group.create_subgroup(), + aio.call, + self._predict_cb, + *args, + **kwargs + ) + raise NotImplementedError() + + +def assert_event(event, event_type, payload, source_timestamp=None): + assert event.type == event_type + assert event.source_timestamp == source_timestamp + assert event.payload.data == payload + + +def conf(): + return { + "event_prefixes": { + "create_instance": ["create_instance"], + "add_instance": ["add_instance"], + "update_instance": ["update_instance"], + "fit": ["fit"], + "predict": ["predict"], + "cancel": ["cancel"], + }, + "state_event_type": ["state"], + "action_state_event_type": ["action_state"], + } + + +@pytest.mark.timeout(1) +async def test_state(): + client = MockClient() + engine = MockEngine() + control = await aimm.server.control.event.create(conf(), engine, client) + events = await client._register_queue.get() + assert len(events) == 1 + assert_event(events[0], ("state",), {"models": {}, "actions": {}}) + + await control.async_close() + + +@pytest.mark.timeout(1) +async def test_create_instance(): + create_queue = aio.Queue() + + async def create_instance_cb(model_type, *c_args, **c_kwargs): + complete_future = asyncio.Future() + create_queue.put_nowait( + { + "model_type": model_type, + "args": c_args, + "kwargs": c_kwargs, + "complete_future": complete_future, + } + ) + return await complete_future + + client = MockClient() + engine = MockEngine(create_instance_cb=create_instance_cb) + control = await aimm.server.control.event.create(conf(), engine, client) + events = await client._register_queue.get() # state + assert events is not None and list(events) != [] + + args = ["a1", "a2"] + kwargs = {"k1": "1"} + req_event = _event( + ("create_instance",), + { + "model_type": "Model1", + "args": args, + "kwargs": kwargs, + "request_id": "1", + }, + ) + await control.process_events([req_event]) + call = await create_queue.get() + assert call["model_type"] == "Model1" + assert call["args"] == tuple(args) + assert call["kwargs"] == kwargs + + call["complete_future"].set_result( + common.Model(instance=None, instance_id=1, model_type="model") + ) + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "IN_PROGRESS", + "result": None, + } + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "DONE", + "result": 1, + } + await control.async_close() + + +@pytest.mark.timeout(10) +async def test_add_instance(plugin_teardown): + @plugins.deserialize(["Model1"]) + def deserialize(instance_bytes): + return instance_bytes.decode("utf-8") + + add_queue = aio.Queue() + + async def add_instance_cb(model_type, instance): + complete_future = asyncio.Future() + add_queue.put_nowait( + { + "instance": instance, + "model_type": model_type, + "complete_future": complete_future, + } + ) + return await complete_future + + client = MockClient() + engine = MockEngine(add_instance_cb=add_instance_cb) + control = await aimm.server.control.event.create(conf(), engine, client) + await client._register_queue.get() # state + + req_event = _event( + ("add_instance",), + { + "model_type": "Model1", + "instance": base64.b64encode("xyz".encode("utf-8")).decode( + "utf-8" + ), + "request_id": "1", + }, + ) + await control.process_events([req_event]) + + call = await add_queue.get() + assert call["model_type"] == "Model1" + assert call["instance"] == "xyz" + call["complete_future"].set_result( + common.Model(instance="xyz", instance_id=2, model_type="Model1") + ) + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "DONE", + "result": 2, + } + + await control.async_close() + + +@pytest.mark.timeout(1) +async def test_update_instance(plugin_teardown): + @plugins.deserialize(["Model1"]) + def deserialize(instance_bytes): + return instance_bytes.decode("utf-8") + + update_queue = aio.Queue() + + async def update_instance_cb(model): + complete_future = asyncio.Future() + update_queue.put_nowait( + {"model": model, "complete_future": complete_future} + ) + return await complete_future + + client = MockClient() + engine = MockEngine(update_instance_cb=update_instance_cb) + control = await aimm.server.control.event.create(conf(), engine, client) + await client._register_queue.get() # state + + req_event = _event( + ("update_instance", "10"), + { + "model_type": "Model1", + "instance": base64.b64encode("xyz".encode("utf-8")).decode( + "utf-8" + ), + "request_id": "1", + }, + ) + await control.process_events([req_event]) + + call = await update_queue.get() + assert call["model"] == common.Model( + model_type="Model1", instance="xyz", instance_id=10 + ) + call["complete_future"].set_result(call["model"]) + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "DONE", + "result": None, + } + await control.async_close() + + +@pytest.mark.timeout(1) +async def test_fit(): + fit_queue = aio.Queue() + + async def fit_cb(model_id, *args, **kwargs): + done_future = asyncio.Future() + fit_queue.put_nowait( + { + "done_future": done_future, + "args": args, + "kwargs": kwargs, + "model_id": model_id, + } + ) + return await done_future + + client = MockClient() + engine = MockEngine( + {"models": {11: common.Model("M", "test", 11)}, "actions": {}}, + fit_cb=fit_cb, + ) + control = await aimm.server.control.event.create(conf(), engine, client) + + await client._register_queue.get() # state + + req_event = _event( + ("fit", "11"), + { + "args": ["a", "b"], + "kwargs": {"c": "d", "e": "f"}, + "request_id": "1", + }, + ) + await control.process_events([req_event]) + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "IN_PROGRESS", + "result": None, + } + + call = await fit_queue.get() + assert call["model_id"] == 11 + assert call["args"] == ("a", "b") + assert call["kwargs"] == {"c": "d", "e": "f"} + + call["done_future"].set_result("fitted instance") + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "DONE", + "result": None, + } + await control.async_close() + + +@pytest.mark.timeout(1) +async def test_predict(): + predict_queue = aio.Queue() + + async def predict_cb(model_id, *args, **kwargs): + done_future = asyncio.Future() + predict_queue.put_nowait( + { + "done_future": done_future, + "args": args, + "kwargs": kwargs, + "model_id": model_id, + } + ) + return await done_future + + client = MockClient() + engine = MockEngine( + {"models": {12: common.Model("M", "test", 12)}, "actions": {}}, + predict_cb=predict_cb, + ) + control = await aimm.server.control.event.create(conf(), engine, client) + + await client._register_queue.get() # state + + req_event = _event( + ("predict", "12"), + { + "args": ["a", "b"], + "kwargs": {"c": "d", "e": "f"}, + "request_id": "1", + }, + ) + await control.process_events([req_event]) + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "IN_PROGRESS", + "result": None, + } + + call = await predict_queue.get() + assert call["model_id"] == 12 + assert call["args"] == ("a", "b") + assert call["kwargs"] == {"c": "d", "e": "f"} + + call["done_future"].set_result("prediction") + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "DONE", + "result": "prediction", + } + await control.async_close() + + +@pytest.mark.timeout(1) +async def test_cancel(): + future_queue = aio.Queue() + + async def predict_cb(_, *__, **___): + done_future = asyncio.Future() + future_queue.put_nowait(done_future) + return await done_future + + client = MockClient() + engine = MockEngine( + {"models": {12: common.Model("M", "test", 12)}, "actions": {}}, + predict_cb=predict_cb, + ) + control = await aimm.server.control.event.create(conf(), engine, client) + + await client._register_queue.get() # state + + req_event = _event( + ("predict", "12"), {"args": [], "kwargs": {}, "request_id": "1"} + ) + await control.process_events([req_event]) + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "IN_PROGRESS", + "result": None, + } + + future = await future_queue.get() + + await control.process_events([_event(("cancel",), "1")]) + + with pytest.raises(asyncio.CancelledError): + await future + + events = await client._register_queue.get() + assert len(events) == 1 + event = events[0] + assert event.type == ("action_state",) + assert event.payload.data == { + "request_id": "1", + "status": "CANCELLED", + "result": None, + } + await control.async_close() + + +def _register_event(event_type, payload, source_timestamp=None): + return hat.event.common.RegisterEvent( + type=event_type, + source_timestamp=source_timestamp, + payload=hat.event.common.EventPayload(payload), + ) + + +def _event( + event_type, + payload, + source_timestamp=None, + event_id=hat.event.common.EventId(server=0, instance=0, session=0), +): + return hat.event.common.Event( + id=event_id, + type=event_type, + timestamp=hat.event.common.now(), + source_timestamp=source_timestamp, + payload=hat.event.common.EventPayloadJson(payload), + ) diff --git a/test/test_unit/test_server/test_control/test_repl.py b/test/test_unit/test_server/test_control/test_repl.py new file mode 100644 index 0000000..0d98e1c --- /dev/null +++ b/test/test_unit/test_server/test_control/test_repl.py @@ -0,0 +1,290 @@ +from hat import aio +from hat import util +import asyncio +import hashlib +import pytest + +from aimm import plugins +from aimm.server import common +import aimm.client.repl +import aimm.server.control.repl +import aimm.server.engine + + +class MockEngine(common.Engine): + def __init__( + self, + state=None, + create_instance_cb=None, + add_instance_cb=None, + update_instance_cb=None, + fit_cb=None, + predict_cb=None, + ): + if state is None: + state = {"models": {}, "actions": {}} + self._state = state + self._cb = lambda: None + self._create_instance_cb = create_instance_cb + self._add_instance_cb = add_instance_cb + self._update_instance_cb = update_instance_cb + self._fit_cb = fit_cb + self._predict_cb = predict_cb + self._group = aio.Group() + + @property + def async_group(self): + return self._group + + @property + def state(self): + return self._state + + @state.setter + def state(self, value): + self._state = value + self._cb() + + def subscribe_to_state_change(self, cb): + self._cb = cb + + def cancel(): + self._cb = None + + return util.RegisterCallbackHandle(cancel=cancel) + + def create_instance(self, *args, **kwargs): + if self._create_instance_cb: + return aimm.server.engine._Action( + self._group.create_subgroup(), + aio.call, + self._create_instance_cb, + *args, + **kwargs, + ) + raise NotImplementedError() + + async def add_instance(self, *args, **kwargs): + if self._add_instance_cb: + return await aio.call(self._add_instance_cb, *args, **kwargs) + raise NotImplementedError() + + async def update_instance(self, *args, **kwargs): + if self._update_instance_cb: + return await aio.call(self._update_instance_cb, *args, **kwargs) + raise NotImplementedError() + + def fit(self, *args, **kwargs): + if self._fit_cb: + return aimm.server.engine._Action( + self._group.create_subgroup(), + aio.call, + self._fit_cb, + *args, + **kwargs, + ) + raise NotImplementedError() + + def predict(self, *args, **kwargs): + if self._predict_cb: + return aimm.server.engine._Action( + self._group.create_subgroup(), + aio.call, + self._predict_cb, + *args, + **kwargs, + ) + raise NotImplementedError() + + +@pytest.fixture +def juggler_port(unused_tcp_port): + return unused_tcp_port + + +@pytest.fixture +def conf(juggler_port): + password_hash = hashlib.sha256() + password_hash.update("password".encode("utf-8")) + return { + "server": {"host": "127.0.0.1", "port": juggler_port}, + "users": [{"username": "user", "password": password_hash.hexdigest()}], + } + + +async def test_login(conf, juggler_port, monkeypatch): + engine = MockEngine() + engine.state = {"models": {}, "actions": {}} + control = await aimm.server.control.repl.create(conf, engine, None) + client = aimm.client.repl.AIMM() + with monkeypatch.context() as ctx: + aimm.client.repl.input = input + ctx.setattr(aimm.client.repl, "input", lambda _: "user") + ctx.setattr(aimm.client.repl, "getpass", lambda _: "password") + await client.connect(f"ws://127.0.0.1:{juggler_port}") + await asyncio.sleep(0.3) + assert client.state == {"models": {}, "actions": {}} + await client.async_close() + await control.async_close() + + +async def _connect(username, password, port, monkeypatch): + client = aimm.client.repl.AIMM() + with monkeypatch.context() as ctx: + aimm.client.repl.input = input + ctx.setattr(aimm.client.repl, "input", lambda _: username) + ctx.setattr(aimm.client.repl, "getpass", lambda _: password) + await client.connect(f"ws://127.0.0.1:{port}") + return client + + +@pytest.fixture +def plugins_model1(plugin_teardown): + @plugins.serialize(["Model1"]) + def serialize1(instance): + return instance.encode("utf-8") + + @plugins.deserialize(["Model1"]) + def deserialize1(instance_bytes): + return instance_bytes.decode("utf-8") + + yield + + +async def test_create_instance( + plugins_model1, conf, juggler_port, monkeypatch +): + create_queue = aio.Queue() + + async def create_instance_cb(model_type, *args, **kwargs): + done_future = asyncio.Future() + create_queue.put_nowait( + { + "model_type": model_type, + "args": args, + "kwargs": kwargs, + "done_future": done_future, + } + ) + return await done_future + + engine = MockEngine(create_instance_cb=create_instance_cb) + control = await aimm.server.control.repl.create(conf, engine, None) + client = await _connect("user", "password", juggler_port, monkeypatch) + + args = ["a1", "a2"] + kwargs = {"k1": "1"} + async with aio.Group() as group: + task = group.spawn(client.create_instance, "Model1", *args, **kwargs) + call = await create_queue.get() + assert call["model_type"] == "Model1" + assert call["args"] == tuple(args) + assert call["kwargs"] == kwargs + + call["done_future"].set_result( + common.Model(instance="xyz", instance_id=1, model_type="Model1") + ) + model = await task + assert model + await control.async_close() + + +async def test_add_instance(plugins_model1, conf, juggler_port, monkeypatch): + add_queue = aio.Queue() + + async def add_instance_cb(model_type, instance): + done_future = asyncio.Future() + add_queue.put_nowait( + { + "instance": instance, + "model_type": model_type, + "done_future": done_future, + } + ) + return await done_future + + engine = MockEngine(add_instance_cb=add_instance_cb) + control = await aimm.server.control.repl.create(conf, engine, None) + client = await _connect("user", "password", juggler_port, monkeypatch) + + async with aio.Group() as group: + task = group.spawn(client.add_instance, "Model1", "xyz") + call = await add_queue.get() + assert call["instance"] == "xyz" + assert call["model_type"] == "Model1" + + call["done_future"].set_result( + common.Model(instance="xyz", instance_id=1, model_type="Model1") + ) + model = await task + assert model + await control.async_close() + + +async def test_fit(plugins_model1, conf, juggler_port, monkeypatch): + fit_queue = aio.Queue() + + async def fit_cb(instance_id, *args, **kwargs): + done_future = asyncio.Future() + fit_queue.put_nowait( + { + "instance_id": instance_id, + "args": args, + "kwargs": kwargs, + "done_future": done_future, + } + ) + return await done_future + + engine = MockEngine(fit_cb=fit_cb) + control = await aimm.server.control.repl.create(conf, engine, None) + client = await _connect("user", "password", juggler_port, monkeypatch) + + args = ["a1", "a2"] + kwargs = {"k1": "1"} + async with aio.Group() as group: + task = group.spawn(client.fit, 1, *args, **kwargs) + call = await fit_queue.get() + assert call["instance_id"] == 1 + assert call["args"] == tuple(args) + assert call["kwargs"] == kwargs + + call["done_future"].set_result( + common.Model(instance="xyz", instance_id=1, model_type="Model1") + ) + model = await task + assert model + await control.async_close() + + +async def test_predict(plugins_model1, conf, juggler_port, monkeypatch): + predict_queue = aio.Queue() + + async def predict_cb(instance_id, *args, **kwargs): + done_future = asyncio.Future() + predict_queue.put_nowait( + { + "instance_id": instance_id, + "args": args, + "kwargs": kwargs, + "done_future": done_future, + } + ) + return await done_future + + engine = MockEngine(predict_cb=predict_cb) + control = await aimm.server.control.repl.create(conf, engine, None) + client = await _connect("user", "password", juggler_port, monkeypatch) + + args = ["a1", "a2"] + kwargs = {"k1": "1"} + async with aio.Group() as group: + task = group.spawn(client.predict, 1, *args, **kwargs) + call = await predict_queue.get() + assert call["instance_id"] == 1 + assert call["args"] == tuple(args) + assert call["kwargs"] == kwargs + + call["done_future"].set_result([1, 2, 3, 4]) + assert await task == [1, 2, 3, 4] + await control.async_close() diff --git a/test/test_unit/test_server/test_engine.py b/test/test_unit/test_server/test_engine.py new file mode 100644 index 0000000..06c9ba3 --- /dev/null +++ b/test/test_unit/test_server/test_engine.py @@ -0,0 +1,220 @@ +from hat import aio +import pytest + +from aimm.server import engine +from aimm.server import common +from aimm import plugins + + +class MockBackend(common.Backend): + def __init__(self, models=None): + self._group = aio.Group() + self._models = models or [] + self._queue = aio.Queue() + + @property + def async_group(self): + return self._group + + @property + def queue(self): + return self._queue + + async def get_models(self): + return self._models + + async def create_model(self, model_type, instance): + model = common.Model( + instance=instance, + model_type=model_type, + instance_id=len(self._models) + 1, + ) + self._models.append(model) + self._queue.put_nowait(("create", model)) + return model + + async def update_model(self, model): + index = None + for i, m in enumerate(self._models): + if m.instance_id == model.instance_id: + index = i + if index is not None: + self._models[index] = model + self._queue.put_nowait(("update", model)) + + +async def create_engine(backend=None): + backend = backend or MockBackend() + return await engine.create( + { + "sigterm_timeout": 1, + "max_children": 1, + "check_children_period": 0.2, + }, + backend, + ) + + +async def test_empty(): + eng = await create_engine() + assert eng.state == {"actions": {}, "models": {}} + await eng.async_close() + + +async def test_models_in_backend(): + models = { + 1: common.Model(instance=None, model_type="test", instance_id=1), + 2: common.Model(instance=None, model_type="test", instance_id=2), + } + eng = await create_engine(MockBackend(models.values())) + assert eng.state == {"actions": {}, "models": models} + await eng.async_close() + + +@pytest.mark.timeout(2) +async def test_create_instance(plugin_teardown): + backend = MockBackend() + eng = await create_engine(backend) + state_queue = aio.Queue() + + eng.subscribe_to_state_change(lambda: state_queue.put_nowait(eng.state)) + + @plugins.instantiate("test") + def create(*c_args, **c_kwargs): + return "test", c_args, c_kwargs + + args = (1, 2, 3) + kwargs = {"p1": 4, "p2": 5} + action = eng.create_instance("test", *args, **kwargs) + model_id = 1 + expected_model = common.Model( + instance=("test", args, kwargs), + instance_id=model_id, + model_type="test", + ) + await state_queue.get() + while True: + state = await state_queue.get() + if model_id in state["models"]: + break + assert state["models"][model_id] == expected_model + assert await action.wait_result() == expected_model + assert await backend.queue.get() == ("create", expected_model) + await eng.async_close() + + +@pytest.mark.timeout(2) +async def test_add_instance(plugin_teardown): + backend = MockBackend() + eng = await create_engine(backend) + states = [] + + queue = aio.Queue() + + def state_change_cb(): + states.append(eng.state) + queue.put_nowait(True) + + eng.subscribe_to_state_change(state_change_cb) + + await eng.add_instance("test", None) + await queue.get() + + instance_id = 1 + assert states == [ + { + "actions": {}, + "models": { + instance_id: common.Model( + instance=None, model_type="test", instance_id=instance_id + ) + }, + } + ] + assert await backend.queue.get() == ( + "create", + common.Model( + instance=None, model_type="test", instance_id=instance_id + ), + ) + await eng.async_close() + + +@pytest.mark.timeout(2) +async def test_fit(plugin_teardown): + backend = MockBackend() + eng = await create_engine(backend) + + queue = aio.Queue() + + def state_change_cb(): + queue.put_nowait(eng.state) + + eng.subscribe_to_state_change(state_change_cb) + + await eng.add_instance("test", "instance") + await queue.get() + await backend.queue.get() + + @plugins.fit(["test"]) + def fit(*f_args, **f_kwargs): + return "instance_fitted", f_args, f_kwargs + + args = (1, 2) + kwargs = {"p1": 3, "p2": 4} + + action = eng.fit(1, *args, **kwargs) + expected_model = common.Model( + instance=("instance_fitted", ("instance", *args), kwargs), + model_type="test", + instance_id=1, + ) + model = await action.wait_result() + assert model == expected_model + + assert queue.get_nowait_until_empty()["models"] == {1: expected_model} + assert await backend.queue.get() == ("update", expected_model) + + # allow model lock to release + await eng.async_close() + + +# @pytest.mark.timeout(2) +async def test_predict(plugin_teardown): + backend = MockBackend() + eng = await create_engine(backend) + + queue = aio.Queue() + + def state_change_cb(): + queue.put_nowait(eng.state) + + eng.subscribe_to_state_change(state_change_cb) + + await eng.add_instance("test", ["instance"]) + await queue.get() + await backend.queue.get() + + @plugins.predict(["test"]) + def predict(instance, *p_args, **p_kwargs): + instance.append(1) + return instance, p_args, p_kwargs + + args = (1, 2) + kwargs = {"p1": 3, "p2": 4} + + action = eng.predict(1, *args, **kwargs) + expected_result = (["instance", 1], args, kwargs) + result = await action.wait_result() + assert result == expected_result + + expected_model = common.Model( + instance=["instance", 1], model_type="test", instance_id=1 + ) + + state = queue.get_nowait_until_empty() + assert state["models"] == {1: expected_model} + predict_backend_update = await backend.queue.get() + assert predict_backend_update == ("update", expected_model) + + await eng.async_close() diff --git a/test/test_unit/test_server/test_mprocess.py b/test/test_unit/test_server/test_mprocess.py new file mode 100644 index 0000000..3bef8d4 --- /dev/null +++ b/test/test_unit/test_server/test_mprocess.py @@ -0,0 +1,115 @@ +from hat import aio +from pytest_cov.embed import cleanup_on_signal +import asyncio +import contextlib +import pytest +import signal +import time + +from aimm.server import mprocess +from aimm.server.mprocess import ProcessTerminatedException + + +@pytest.fixture +def disable_sigterm_handler(monkeypatch): + default = mprocess.sigterm_override + + @contextlib.contextmanager + def handler_patch(): + cleanup_on_signal(signal.SIGTERM) + with default(): + yield + + with monkeypatch.context() as ctx: + ctx.setattr(mprocess, "sigterm_override", handler_patch) + yield + + +@pytest.mark.timeout(2) +@pytest.mark.parametrize("action_count", [1, 2, 10]) +async def test_process_regular(action_count, disable_sigterm_handler): + def fn(*f_args, **f_kwargs): + return f_args, f_kwargs + + args = ("arg1", "arg2") + kwargs = {"k1": "v1", "k2": "v2"} + + pa_pool = mprocess.ProcessManager(1, aio.Group(), 0.1, 2) + async with aio.Group() as group: + tasks = [] + for _ in range(action_count): + process_action = pa_pool.create_handler(lambda _: None) + tasks.append(group.spawn(process_action.run, fn, *args, **kwargs)) + await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) + + assert [t.result() for t in tasks] == [(args, kwargs)] * action_count + await process_action.wait_closed() + + await pa_pool.async_close() + + +@pytest.mark.timeout(2) +async def test_process_exception(disable_sigterm_handler): + exception_text = "test exception" + + def fn(): + raise Exception(exception_text) + + pa_pool = mprocess.ProcessManager(1, aio.Group(), 0.1, 2) + process_action = pa_pool.create_handler(lambda _: None) + + with pytest.raises(Exception, match=exception_text): + await process_action.run(fn) + await process_action.wait_closed() + + await pa_pool.async_close() + + +@pytest.mark.timeout(2) +async def test_process_sigterm(disable_sigterm_handler): + def fn(): + time.sleep(10) + + pa_pool = mprocess.ProcessManager(1, aio.Group(), 0.1, 5) + process_action = pa_pool.create_handler(lambda _: None) + async with aio.Group() as group: + + async def _run(): + with pytest.raises( + ProcessTerminatedException, match="process sigterm" + ): + await process_action.run(fn) + + task = group.spawn(_run) + await asyncio.sleep(0.2) + await process_action.async_close() + await task + assert not task.exception() + + await pa_pool.async_close() + + +@pytest.mark.timeout(2) +async def test_process_sigkill(): + def fn(): + try: + time.sleep(10) + except ProcessTerminatedException: + time.sleep(10) + raise Exception("unexpected exception") + + pa_pool = mprocess.ProcessManager(1, aio.Group(), 0.1, 0.2) + process_action = pa_pool.create_handler(lambda _: None) + async with aio.Group() as group: + + async def _run(): + with pytest.raises(ProcessTerminatedException): + await process_action.run(fn) + + task = group.spawn(_run) + await asyncio.sleep(0.2) + await process_action.async_close() + await task + assert not task.exception() + + await pa_pool.async_close()