From dff0844240a48905eb219a14062048842dc02463 Mon Sep 17 00:00:00 2001 From: Weiyuan Wu Date: Sun, 13 Sep 2020 12:09:46 -0700 Subject: [PATCH] feat(connector): use pydantic for schema --- dataprep/connector/connector.py | 142 +++++-- dataprep/connector/implicit_database.py | 142 ++----- dataprep/connector/schema.json | 301 -------------- dataprep/connector/schema.py | 8 - dataprep/connector/schema/__init__.py | 3 + dataprep/connector/schema/base.py | 108 +++++ dataprep/connector/schema/defs.py | 201 ++++++++++ dataprep/connector/types.py | 170 -------- poetry.lock | 498 +++++++++++++++++------- pyproject.toml | 3 + 10 files changed, 810 insertions(+), 766 deletions(-) delete mode 100644 dataprep/connector/schema.json delete mode 100644 dataprep/connector/schema.py create mode 100644 dataprep/connector/schema/__init__.py create mode 100644 dataprep/connector/schema/base.py create mode 100644 dataprep/connector/schema/defs.py delete mode 100644 dataprep/connector/types.py diff --git a/dataprep/connector/connector.py b/dataprep/connector/connector.py index 76637cc90..0dd53dddb 100644 --- a/dataprep/connector/connector.py +++ b/dataprep/connector/connector.py @@ -6,17 +6,18 @@ import sys from asyncio import as_completed from pathlib import Path -from typing import Any, Awaitable, Dict, List, Optional, Union +from typing import Any, Awaitable, Dict, List, Optional, Union, cast import pandas as pd from aiohttp import ClientSession -from jinja2 import Environment, StrictUndefined, Template +from jinja2 import Environment, StrictUndefined, Template, UndefinedError from ..errors import UnreachableError from .config_manager import config_directory, ensure_config from .errors import RequestError, UniversalParameterOverridden, InvalidParameterError from .implicit_database import ImplicitDatabase, ImplicitTable from .int_ref import IntRef +from .schema import ConfigDef, FieldDef from .throttler import OrderedThrottler, ThrottleSession INFO_TEMPLATE = Template( @@ -38,8 +39,7 @@ class Connector: - """ - This is the main class of the connector component. + """This is the main class of the connector component. Initialize Connector class as the example code. Parameters @@ -61,8 +61,11 @@ class Connector: """ _impdb: ImplicitDatabase + # Varibles that used across different queries, can be overriden by query _vars: Dict[str, Any] _auth: Dict[str, Any] + # storage for authorization + _storage: Dict[str, Any] _concurrency: int _jenv: Environment @@ -88,6 +91,7 @@ def __init__( self._vars = kwargs self._auth = _auth or {} + self._storage = {} self._concurrency = _concurrency self._jenv = Environment(undefined=StrictUndefined) self._throttler = OrderedThrottler(_concurrency) @@ -116,7 +120,7 @@ async def query( # pylint: disable=too-many-locals **where The additional parameters required for the query. """ - allowed_params = self._impdb.tables[table].config["request"]["params"] + allowed_params = self._impdb.tables[table].config.request.params for key in where: if key not in allowed_params: raise InvalidParameterError(key) @@ -142,12 +146,12 @@ def info(self) -> None: # get info tbs: Dict[str, Any] = {} for cur_table in self._impdb.tables: - table_config_content = self._impdb.tables[cur_table].config + table_config_content: ConfigDef = self._impdb.tables[cur_table].config params_required = [] params_optional = [] example_query_fields = [] count = 1 - for k, val in table_config_content["request"]["params"].items(): + for k, val in table_config_content.request.params.items(): if isinstance(val, bool) and val: params_required.append(k) example_query_fields.append(f"""{k}="word{count}\"""") @@ -167,31 +171,33 @@ def info(self) -> None: ) def show_schema(self, table_name: str) -> pd.DataFrame: - """ - This method shows the schema of the table that will be returned, + """This method shows the schema of the table that will be returned, so that the user knows what information to expect. + Parameters ---------- table_name The table name. + Returns ------- pd.DataFrame The returned data's schema. + Note ---- The schema is defined in the configuration file. The user can either use the default one or change it by editing the configuration file. """ print(f"table: {table_name}") - table_config_content = self._impdb.tables[table_name].config - schema = table_config_content["response"]["schema"] + table_config_content: ConfigDef = self._impdb.tables[table_name].config + schema = table_config_content.response.schema_ new_schema_dict: Dict[str, List[Any]] = {} new_schema_dict["column_name"] = [] new_schema_dict["data_type"] = [] for k in schema.keys(): new_schema_dict["column_name"].append(k) - new_schema_dict["data_type"].append(schema[k]["type"]) + new_schema_dict["data_type"].append(schema[k].type) return pd.DataFrame.from_dict(new_schema_dict) async def _query_imp( # pylint: disable=too-many-locals,too-many-branches @@ -206,7 +212,9 @@ async def _query_imp( # pylint: disable=too-many-locals,too-many-branches raise ValueError(f"No such table {table} in {self._impdb.name}") itable = self._impdb.tables[table] - if itable.pag_params is None and _count is not None: + reqconf = itable.config.request + + if reqconf.pagination is None and _count is not None: print( f"ignoring _count since {table} has no pagination settings", file=sys.stderr, @@ -216,22 +224,23 @@ async def _query_imp( # pylint: disable=too-many-locals,too-many-branches raise RuntimeError("_count should be larger than 0") async with ClientSession() as client: + throttler = self._throttler.session() - if itable.pag_params is None or _count is None: + if reqconf.pagination is None or _count is None: df = await self._fetch( itable, kwargs, _client=client, _throttler=throttler, _auth=_auth, ) return df - pag_type = itable.pag_params.type + pagdef = reqconf.pagination # pagination begins - max_per_page = itable.pag_params.max_count + max_per_page = pagdef.max_count total = _count n_page = math.ceil(total / max_per_page) - if pag_type == "seek": + if pagdef.type == "seek": last_id = 0 dfs = [] # No way to parallelize for seek type @@ -255,10 +264,10 @@ async def _query_imp( # pylint: disable=too-many-locals,too-many-branches # The API returns empty for this page, maybe we've reached the end break - last_id = int(df[itable.pag_params.seek_id][len(df) - 1]) - 1 + last_id = int(df[pagdef.seek_id][len(df) - 1]) - 1 dfs.append(df) - elif pag_type == "offset": + elif pagdef.type == "offset": resps_coros = [] allowed_page = IntRef(n_page) for i in range(n_page): @@ -290,7 +299,7 @@ async def _query_imp( # pylint: disable=too-many-locals,too-many-branches return df - async def _fetch( # pylint: disable=too-many-locals,too-many-branches + async def _fetch( # pylint: disable=too-many-locals,too-many-branches,too-many-statements self, table: ImplicitTable, kwargs: Dict[str, Any], @@ -306,8 +315,9 @@ async def _fetch( # pylint: disable=too-many-locals,too-many-branches if (_limit is None) != (_offset is None): raise ValueError("_limit and _offset should both be None or not None") - method = table.method - url = table.url + reqdef = table.config.request + method = reqdef.method + url = reqdef.url req_data: Dict[str, Dict[str, Any]] = { "headers": {}, "params": {}, @@ -315,41 +325,43 @@ async def _fetch( # pylint: disable=too-many-locals,too-many-branches } merged_vars = {**self._vars, **kwargs} - if table.authorization is not None: - table.authorization.build(req_data, _auth or self._auth) + if reqdef.authorization is not None: + reqdef.authorization.build(req_data, _auth or self._auth, self._storage) for key in ["headers", "params", "cookies"]: - if getattr(table, key) is not None: - instantiated_fields = getattr(table, key).populate( - self._jenv, merged_vars - ) + field_def = getattr(reqdef, key, None) + if field_def is not None: + instantiated_fields = populate_field(field_def, self._jenv, merged_vars) req_data[key].update(**instantiated_fields) - if table.body is not None: + if reqdef.body is not None: # TODO: do we support binary body? - instantiated_fields = table.body.populate(self._jenv, merged_vars) - if table.body_ctype == "application/x-www-form-urlencoded": + instantiated_fields = populate_field( + reqdef.body.content, self._jenv, merged_vars + ) + if reqdef.body.ctype == "application/x-www-form-urlencoded": req_data["data"] = instantiated_fields - elif table.body_ctype == "application/json": + elif reqdef.body.ctype == "application/json": req_data["json"] = instantiated_fields else: - raise NotImplementedError(table.body_ctype) + raise NotImplementedError(reqdef.body.ctype) - if table.pag_params is not None and _limit is not None: - pag_type = table.pag_params.type - limit_key = table.pag_params.limit_key + if reqdef.pagination is not None and _limit is not None: + pagdef = reqdef.pagination + pag_type = pagdef.type + limit_key = pagdef.limit_key if pag_type == "seek": - if table.pag_params.seek_key is None: + if pagdef.seek_key is None: raise ValueError( "pagination type is seek but no seek_key set in the configuration file." ) - offset_key = table.pag_params.seek_key + offset_key = pagdef.seek_key elif pag_type == "offset": - if table.pag_params.offset_key is None: + if pagdef.offset_key is None: raise ValueError( "pagination type is offset but no offset_key set in the configuration file." ) - offset_key = table.pag_params.offset_key + offset_key = pagdef.offset_key else: raise UnreachableError() @@ -387,3 +399,53 @@ async def _fetch( # pylint: disable=too-many-locals,too-many-branches return None else: return df + + +def populate_field( # pylint: disable=too-many-branches + fields: Dict[str, FieldDef], jenv: Environment, params: Dict[str, Any] +) -> Dict[str, str]: + """Populate a dict based on the fields definition and provided vars.""" + + ret: Dict[str, str] = {} + + for key, def_ in fields.items(): + from_key, to_key = key, key + + if isinstance(def_, bool): + required = def_ + value = params.get(from_key) + if value is None and required: + raise KeyError(from_key) + remove_if_empty = False + elif isinstance(def_, str): + # is a template + template: Optional[str] = def_ + tmplt = jenv.from_string(cast(str, template)) + value = tmplt.render(**params) + remove_if_empty = False + else: + template = def_.template + remove_if_empty = def_.remove_if_empty + to_key = def_.to_key or to_key + from_key = def_.from_key or from_key + + if template is None: + required = def_.required + value = params.get(from_key) + if value is None and required: + raise KeyError(from_key) + else: + tmplt = jenv.from_string(template) + try: + value = tmplt.render(**params) + except UndefinedError: + value = "" # This empty string will be removed if `remove_if_empty` is True + + if value is not None: + str_value = str(value) + if not (remove_if_empty and not str_value): + if to_key in ret: + print(f"Param {key} conflicting with {to_key}", file=sys.stderr) + ret[to_key] = str_value + continue + return ret diff --git a/dataprep/connector/implicit_database.py b/dataprep/connector/implicit_database.py index b124ba9f5..d9c2afc37 100644 --- a/dataprep/connector/implicit_database.py +++ b/dataprep/connector/implicit_database.py @@ -7,16 +7,16 @@ from json import load as jload from json import loads as jloads from pathlib import Path -from typing import Any, Dict, List, NamedTuple, Optional, Union +from typing import Any, Dict, List, Union -import jsonschema import pandas as pd from jsonpath_ng import parse as jparse from lxml import etree # pytype: disable=import-error +from dataprep.connector.schema.defs import ConfigDef + from ..errors import UnreachableError -from .schema import CONFIG_SCHEMA -from .types import Authorization, AuthorizationType, Fields, Orient +from .schema import ConfigDef _TYPE_MAPPING = { "int": int, @@ -26,111 +26,25 @@ } -class SchemaField(NamedTuple): - """Schema of one table field.""" - - target: str - type: str - description: Optional[str] - - -class Pagination: - """Schema of Pagination field.""" - - type: str - limit_key: str - max_count: int - offset_key: Optional[str] - seek_id: Optional[str] - seek_key: Optional[str] - - def __init__(self, pdef: Dict[str, Any]) -> None: - - self.type = pdef["type"] - self.max_count = pdef["max_count"] - self.limit_key = pdef["limit_key"] - self.offset_key = pdef.get("offset_key") - self.seek_id = pdef.get("seek_id") - self.seek_key = pdef.get("seek_key") - - class ImplicitTable: # pylint: disable=too-many-instance-attributes - """ - ImplicitTable class abstracts the request and the response to a Restful API, - so that the remote API can be treated as a database table. - """ + """ImplicitTable class abstracts the request and the response + to a Restful API, so that the remote API can be treated as a database + table.""" name: str - config: Dict[str, Any] - # Request related - method: str - url: str - authorization: Optional[Authorization] = None - headers: Optional[Fields] = None - params: Optional[Fields] = None - body_ctype: str - body: Optional[Fields] = None - cookies: Optional[Fields] = None - pag_params: Optional[Pagination] = None - - # Response related - ctype: str - table_path: str - schema: Dict[str, SchemaField] - orient: Orient + config: ConfigDef def __init__(self, name: str, config: Dict[str, Any]) -> None: - jsonschema.validate( - config, CONFIG_SCHEMA - ) # This will throw errors if validate failed self.name = name - self.config = config - - request_def = config["request"] - - self.method = request_def["method"] - self.url = request_def["url"] - - if "authorization" in request_def: - auth_def = request_def["authorization"] - if isinstance(auth_def, str): - auth_type = AuthorizationType[auth_def] - auth_params: Dict[str, str] = {} - elif isinstance(auth_def, dict): - auth_type = AuthorizationType[auth_def.pop("type")] - auth_params = {**auth_def} - else: - raise NotImplementedError - self.authorization = Authorization(auth_type=auth_type, params=auth_params) - - if "pagination" in request_def: - self.pag_params = Pagination(request_def["pagination"]) - - for key in ["headers", "params", "cookies"]: - if key in request_def: - setattr(self, key, Fields(request_def[key])) - - if "body" in request_def: - body_def = request_def["body"] - self.body_ctype = body_def["ctype"] - self.body = Fields(body_def["content"]) - - response_def = config["response"] - self.ctype = response_def["ctype"] - self.table_path = response_def["tablePath"] - self.schema = { - name: SchemaField(def_["target"], def_["type"], def_.get("description")) - for name, def_ in response_def["schema"].items() - } - self.orient = Orient(response_def["orient"]) + self.config = ConfigDef(**config) def from_response(self, payload: str) -> pd.DataFrame: - """ - Create a dataframe from a http body payload. - """ - if self.ctype == "application/json": + """Create a dataframe from a http body payload.""" + + ctype = self.config.response.ctype # pylint: disable=no-member + if ctype == "application/json": rows = self.from_json(payload) - elif self.ctype == "application/xml": + elif ctype == "application/xml": rows = self.from_xml(payload) else: raise UnreachableError @@ -138,17 +52,20 @@ def from_response(self, payload: str) -> pd.DataFrame: return pd.DataFrame(rows) def from_json(self, data: str) -> Dict[str, List[Any]]: - """ - Create rows from json string. - """ + """Create rows from json string.""" + data = jloads(data) table_data = {} - table_expr = jparse(self.table_path) + respdef = self.config.response + table_expr = jparse(respdef.table_path) # pylint: disable=no-member - if self.orient == Orient.Records: + if respdef.orient == "records": # pylint: disable=no-member data_rows = [match.value for match in table_expr.find(data)] - for column_name, column_def in self.schema.items(): + for ( + column_name, + column_def, + ) in respdef.schema_.items(): # pylint: disable=no-member column_target = column_def.target column_type = column_def.type @@ -185,14 +102,17 @@ def from_xml(self, data: str) -> Dict[str, List[Any]]: Create rows from xml string. """ table_data = {} - + respdef = self.config.response data = data.replace('', "") root = etree.parse(StringIO(data)) - data_rows = root.xpath(self.table_path) + data_rows = root.xpath(respdef.table_path) # pylint: disable=no-member - if self.orient.value == Orient.Records.value: - for column_name, column_def in self.schema.items(): + if respdef.orient == "records": # pylint: disable=no-member + for ( + column_name, + column_def, + ) in respdef.schema_.items(): # pylint: disable=no-member column_target = column_def.target column_type = column_def.type @@ -246,7 +166,7 @@ def __init__(self, config_path: Union[str, Path]) -> None: # ignore meta file continue if table_config_path.suffix != ".json": - # ifnote non json file + # ignote non json file continue with open(table_config_path) as f: diff --git a/dataprep/connector/schema.json b/dataprep/connector/schema.json deleted file mode 100644 index b0379bdc0..000000000 --- a/dataprep/connector/schema.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://example.com/root.json", - "type": "object", - "title": "The config for a connector", - "required": [ - "version", - "request", - "response" - ], - "additionalProperties": false, - "properties": { - "version": { - "$id": "#/properties/version", - "type": "number", - "title": "The Version Schema", - "description": "The version number of the schema", - "default": 1, - "minimum": 1 - }, - "request": { - "$id": "#/properties/request", - "type": "object", - "title": "The Request Schema", - "description": "", - "required": [ - "url", - "method" - ], - "properties": { - "url": { - "$id": "#/properties/request/properties/url", - "type": "string", - "title": "The Url Schema", - "description": "The Url of the API endpoint. This can also be a Jinja template", - "default": "", - "examples": [ - "http://example.com/api" - ], - "format": "uri" - }, - "method": { - "$id": "#/properties/request/properties/method", - "type": "string", - "title": "The Method Schema", - "examples": [ - "GET" - ], - "enum": [ - "GET", - "POST", - "PUT" - ] - }, - "authorization": { - "$ref": "#/definitions/authorization" - }, - "headers": { - "$ref": "#/definitions/fields" - }, - "params": { - "$ref": "#/definitions/fields" - }, - "pagination": { - "$id": "#/properties/request/properties/pagination", - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "maxCount": { - "type": "integer" - }, - "offsetKey": { - "type": "string", - "optional": true - }, - "limitKey": { - "type": "string" - }, - "seekId": { - "type": "string", - "optional": true - }, - "seekKey": { - "type": "string", - "optional": true - } - }, - "required": [ - "limitKey", - "type", - "maxCount" - ], - "additionalProperties": false - }, - "body": { - "$id": "#/properties/request/properties/body", - "type": "object", - "title": "The Body Schema", - "properties": { - "ctype": { - "$id": "#/properties/request/properties/body/properties/ctype", - "type": "string", - "title": "The content type schema", - "default": "application/json", - "enum": [ - "application/x-www-form-urlencoded", - "application/json" - ] - }, - "content": { - "$ref": "#/definitions/fields" - } - } - }, - "cookies": { - "$ref": "#/definitions/fields" - } - }, - "additionalProperties": false - }, - "response": { - "$id": "#/properties/response", - "type": "object", - "title": "The Response Schema", - "required": [ - "ctype", - "tablePath", - "schema" - ], - "properties": { - "ctype": { - "$id": "#/properties/response/properties/ctype", - "type": "string", - "title": "The Response Content Type Schema", - "default": "application/json", - "enum": [ - "application/x-www-form-urlencoded", - "application/json", - "application/xml" - ] - }, - "tablePath": { - "$id": "#/properties/response/properties/tablePath", - "type": "string", - "title": "The Path to the Table Object", - "default": "" - }, - "schema": { - "$ref": "#/definitions/schema" - }, - "orient": { - "$id": "#/properties/response/properties/orient", - "type": "string", - "title": "The Orient for the Table", - "default": "records", - "enum": [ - "split", - "records" - ] - } - }, - "additionalProperties": false - }, - "additionalProperties": false - }, - "definitions": { - "fields": { - "$id": "#/definitions/fields", - "type": "object", - "title": "Spec for Fields Definition", - "additionalProperties": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "boolean" - }, - { - "type": "object", - "required": [ - "required", - "removeIfEmpty" - ], - "properties": { - "required": { - "type": "boolean", - "default": false - }, - "fromKey": { - "type": "string" - }, - "toKey": { - "type": "string" - }, - "template": { - "type": "string" - }, - "removeIfEmpty": { - "type": "boolean", - "default": false - }, - "additionalProperties": false - } - } - ] - } - }, - "authorization": { - "$id": "#/definitions/authorization", - "oneOf": [ - { - "type": "object", - "required": [ - "type", - "grantType", - "tokenServerUrl" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "OAuth2" - ] - }, - "grantType": { - "type": "string", - "enum": [ - "ClientCredentials", - "AuthorizationCode" - ] - }, - "tokenServerUrl": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "type", - "keyParam" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "QueryParam" - ] - }, - "keyParam": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "string", - "enum": [ - "Bearer" - ] - } - ] - }, - "schema": { - "$id": "#/definitions/schema", - "type": "object", - "title": "Spec for table definition", - "additionalProperties": { - "type": "object", - "title": "Spec for schema item", - "properties": { - "target": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "string", - "int", - "float", - "boolean", - "object" - ] - }, - "description": { - "type": "string" - } - }, - "required": [ - "target", - "type" - ], - "additionalProperties": false - } - } - } -} \ No newline at end of file diff --git a/dataprep/connector/schema.py b/dataprep/connector/schema.py deleted file mode 100644 index 209ad76f7..000000000 --- a/dataprep/connector/schema.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Module contains the loaded config schema. -""" -from json import load as jload -from pathlib import Path - -with open(f"{Path(__file__).parent}/schema.json", "r") as f: - CONFIG_SCHEMA = jload(f) diff --git a/dataprep/connector/schema/__init__.py b/dataprep/connector/schema/__init__.py new file mode 100644 index 000000000..67916ba7c --- /dev/null +++ b/dataprep/connector/schema/__init__.py @@ -0,0 +1,3 @@ +"""Module contains the loaded config schema.""" + +from .defs import * diff --git a/dataprep/connector/schema/base.py b/dataprep/connector/schema/base.py new file mode 100644 index 000000000..2c033a708 --- /dev/null +++ b/dataprep/connector/schema/base.py @@ -0,0 +1,108 @@ +"""Base class for schema definition.""" + +# pylint: disable=missing-function-docstring + +from copy import deepcopy +from dataclasses import dataclass +from typing import Any, Callable, Dict, Optional, TypeVar + +from pydantic import BaseModel # pylint: disable=no-name-in-module +from stringcase import camelcase + +T = TypeVar("T") # pylint: disable=invalid-name + + +@dataclass +class Policy: + """Merge policy. Defines how a field can be merged.""" + + override_none: bool = False + merge: str = "same" # merge policy, values: same, override, keep or None + + +class BaseDef(BaseModel): + """The base definition.""" + + __merge_policy__: Dict[str, Policy] = {} + + class Config: # pylint: disable=missing-class-docstring + alias_generator: Callable[[str], str] = camelcase + validate_assignment: bool = True + extra: str = "forbid" + validate_all: bool = True + + def merge(self, rhs: Any) -> "BaseDef": + if not isinstance(rhs, type(self)): + raise ValueError(f"Cannot merge {type(self)} with {type(rhs)}") + + cur: "BaseDef" = self.copy() + + for attr, _ in self.__fields__.items(): + cur_value, rhs_value = getattr(cur, attr), getattr(rhs, attr) + + if cur_value is None and rhs_value is None: + pass + elif (cur_value is None) != (rhs_value is None): + if self.__merge_policy__.get(attr, Policy()).override_none: + setattr(cur, attr, coalesce(cur_value, rhs_value)) + else: + raise ValueError(f"None {attr} cannot be overriden.") + else: + + merged = merge_values( + cur_value, + rhs_value, + attr, + self.__merge_policy__.get(attr, Policy()), + ) + setattr(cur, attr, merged) + + return cur + + +def merge_values( # pylint: disable=too-many-branches + lhs: T, rhs: T, attr: str, policy: Policy +) -> T: + """merge two not none values.""" + + if not isinstance(rhs, type(lhs)): + raise ValueError( + f"Cannot merge {type(lhs)} with {type(rhs)} for {type(lhs).__name__}.{attr}" + ) + + if isinstance(lhs, BaseDef): + return lhs.merge(rhs) + elif isinstance(rhs, dict): + for key in rhs.keys(): + if key in lhs: + lhs[key] = merge_values(lhs[key], rhs[key], attr, policy) + else: + if isinstance(rhs[key], BaseDef): + lhs[key] = rhs[key].copy() + else: + lhs[key] = deepcopy(rhs[key]) + return lhs + elif isinstance(lhs, (int, float, str, bool)): + if policy.merge is None or policy.merge == "same": + if lhs != rhs: + raise ValueError( + f"Cannot merge with different {attr}:{type(lhs).__name__} : {lhs} != {rhs}." + ) + return lhs + elif policy.merge == "override": + return rhs + elif policy.merge == "keep": + return lhs + else: + raise RuntimeError(f"Unknown merge policy {policy.merge}.") + else: + raise RuntimeError(f"Unknown type {type(lhs).__name__}.") + + +def coalesce( # pylint: disable=invalid-name + a: Optional[T], b: Optional[T] +) -> Optional[T]: + if a is None: + return b + else: + return a diff --git a/dataprep/connector/schema/defs.py b/dataprep/connector/schema/defs.py new file mode 100644 index 000000000..4f86f6f32 --- /dev/null +++ b/dataprep/connector/schema/defs.py @@ -0,0 +1,201 @@ +"""Strong typed schema definition.""" + +from base64 import b64encode +from copy import deepcopy +from enum import Enum +from time import time +from typing import Any, Dict, Optional, Union + +import requests +from pydantic import Field + +from .base import BaseDef + + +# pylint: disable=missing-class-docstring,missing-function-docstring +class PaginationDef(BaseDef): + type: str = Field(regex=r"^(offset|seek)$") + max_count: int + offset_key: Optional[str] + limit_key: str + seek_id: Optional[str] + seek_key: Optional[str] + + +class FieldDef(BaseDef): + required: bool + from_key: Optional[str] + to_key: Optional[str] + template: Optional[str] + remove_if_empty: bool + + +FieldDefUnion = Union[FieldDef, bool, str] # Put bool before str + + +class OAuth2AuthorizationDef(BaseDef): + type: str = Field("OAuth2", const=True) + grant_type: str + token_server_url: str + + def build( + self, + req_data: Dict[str, Any], + params: Dict[str, Any], + storage: Optional[Dict[str, Any]] = None, + ) -> None: + if storage is None: + raise ValueError("storage is required for OAuth2") + + if self.grant_type == "ClientCredentials": + if "access_token" not in storage or storage.get("expires_at", 0) < time(): + # Not yet authorized + ckey = params["client_id"] + csecret = params["client_secret"] + b64cred = b64encode(f"{ckey}:{csecret}".encode("ascii")).decode() + resp: Dict[str, Any] = requests.post( + self.token_server_url, + headers={"Authorization": f"Basic {b64cred}"}, + data={"grant_type": "client_credentials"}, + ).json() + if resp["token_type"].lower() != "bearer": + raise RuntimeError("token_type is not bearer") + + access_token = resp["access_token"] + storage["access_token"] = access_token + if "expires_in" in resp: + storage["expires_at"] = ( + time() + resp["expires_in"] - 60 + ) # 60 seconds grace period to avoid clock lag + + req_data["headers"]["Authorization"] = f"Bearer {storage['access_token']}" + + # TODO: handle auto refresh + elif self.grant_type == "AuthorizationCode": + raise NotImplementedError + + +class QueryParamAuthorizationDef(BaseDef): + type: str = Field("QueryParam", const=True) + key_param: str + + def build( + self, + req_data: Dict[str, Any], + params: Dict[str, Any], + storage: Optional[Dict[str, Any]] = None, # pylint: disable=unused-argument + ) -> None: + """Populate some required fields to the request data. + Complex logic may also happens in this function (e.g. start a server to do OAuth). + """ + req_data["params"][self.key_param] = params["access_token"] + + +class BearerAuthorizationDef(BaseDef): + type: str = Field("Bearer", const=True) + + @staticmethod + def build( + req_data: Dict[str, Any], + params: Dict[str, Any], + storage: Optional[Dict[str, Any]] = None, # pylint: disable=unused-argument + ) -> None: + """Populate some required fields to the request data. + Complex logic may also happens in this function (e.g. start a server to do OAuth). + """ + req_data["headers"]["Authorization"] = f"Bearer {params['access_token']}" + + +AuthorizationDef = Union[ + OAuth2AuthorizationDef, QueryParamAuthorizationDef, BearerAuthorizationDef +] + + +class BodyDef(BaseDef): + ctype: str = Field(regex=r"^(application/x-www-form-urlencoded|application/json)$") + content: Dict[str, FieldDefUnion] + + +class Method(str, Enum): + GET = "GET" + POST = "POST" + PUT = "PUT" + + +class RequestDef(BaseDef): + url: str + method: Method + authorization: Optional[AuthorizationDef] + headers: Optional[Dict[str, FieldDefUnion]] + params: Dict[str, FieldDefUnion] + pagination: Optional[PaginationDef] + body: Optional[BodyDef] + cookies: Optional[Dict[str, FieldDefUnion]] + + +class SchemaFieldDef(BaseDef): + target: str + type: str + description: Optional[str] + + def merge(self, rhs: Any) -> "SchemaFieldDef": + if not isinstance(rhs, SchemaFieldDef): + raise ValueError(f"Cannot merge {type(self)} with {type(rhs)}") + + if self.target != rhs.target: + raise ValueError("Cannot merge SchemaFieldDef with different target.") + + merged_type = merge_type(self.type, rhs.type) + + cur = deepcopy(self) + cur.type = merged_type + cur.description = rhs.description + + return cur + + +TYPE_TREE = { + "object": None, + "string": None, + "float": "string", + "int": "float", + "bool": "string", +} + + +def merge_type(a: str, b: str) -> str: # pylint: disable=invalid-name + if a == b: + return a + + aset = {a} + bset = {b} + + while True: + aparent = TYPE_TREE[a] + if aparent is not None: + if aparent in bset: + return aparent + else: + aset.add(aparent) + bparent = TYPE_TREE[b] + if bparent is not None: + if bparent in aset: + return bparent + else: + bset.add(bparent) + + if aparent is None and bparent is None: + raise RuntimeError("Unreachable") + + +class ResponseDef(BaseDef): + ctype: str = Field(regex=r"^(application/xml|application/json)$") + table_path: str + schema_: Dict[str, SchemaFieldDef] = Field(alias="schema") + orient: str = Field(regex=r"^(records|split)$") + + +class ConfigDef(BaseDef): + version: int = Field(1, const=True) + request: RequestDef + response: ResponseDef diff --git a/dataprep/connector/types.py b/dataprep/connector/types.py deleted file mode 100644 index 04005e827..000000000 --- a/dataprep/connector/types.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -Defines useful types in this library. -""" -from base64 import b64encode -from enum import Enum -from time import time -from typing import Any, Dict, Optional, cast -from sys import stderr -import requests -from jinja2 import Environment, UndefinedError - -from ..errors import UnreachableError - - -class AuthorizationType(Enum): - """Enum class defines the supported authorization methods in this library. - - Note - ---- - - * Bearer: requires 'access_token' presented in user params - - added as an Auth Header - * QueryParam: requires 'access_token' presented in user params - - added as a query string parameter - * OAuth2: requires 'client_id' and 'client_secret' in user params for - 'ClientCredentials' grant type - """ - - Bearer = "Bearer" - QueryParam = "QueryParam" - OAuth2 = "OAuth2" - - -class Authorization: - """Class carries the authorization type and - the corresponding parameter. - """ - - auth_type: AuthorizationType - params: Dict[str, str] - storage: Dict[str, Any] - - def __init__(self, auth_type: AuthorizationType, params: Dict[str, str]) -> None: - self.auth_type = auth_type - self.params = params - self.storage = {} - - def build(self, req_data: Dict[str, Any], params: Dict[str, Any]) -> None: - """Populate some required fields to the request data. - Complex logic may also happens in this function (e.g. start a server to do OAuth). - """ - if self.auth_type == AuthorizationType.Bearer: # pylint: disable=no-member - req_data["headers"]["Authorization"] = f"Bearer {params['access_token']}" - elif self.auth_type == AuthorizationType.QueryParam: - req_data["params"][self.params["keyParam"]] = params["access_token"] - elif ( - self.auth_type == AuthorizationType.OAuth2 - and self.params["grantType"] == "ClientCredentials" - ): - # TODO: Move OAuth to a separate authenticator - if ( - "access_token" not in self.storage - or self.storage.get("expires_at", 0) < time() - ): - # Not yet authorized - ckey = params["client_id"] - csecret = params["client_secret"] - b64cred = b64encode(f"{ckey}:{csecret}".encode("ascii")).decode() - resp = requests.post( - self.params["tokenServerUrl"], - headers={"Authorization": f"Basic {b64cred}"}, - data={"grant_type": "client_credentials"}, - ).json() - - assert resp["token_type"].lower() == "bearer" - access_token = resp["access_token"] - self.storage["access_token"] = access_token - if "expires_in" in resp: - self.storage["expires_at"] = ( - time() + resp["expires_in"] - 60 - ) # 60 seconds grace period to avoid clock lag - - req_data["headers"][ - "Authorization" - ] = f"Bearer {self.storage['access_token']}" - - # TODO: handle auto refresh - elif ( - self.auth_type == AuthorizationType.OAuth2 - and self.params["grantType"] == "AuthorizationCode" - ): - raise NotImplementedError - - -class Fields: - """A data structure that stores the fields information (e.g. headers, cookies, ...). - This class is useful to populate concrete fields data with required variables provided. - """ - - fields: Dict[str, Any] - - def __init__(self, fields_config: Dict[str, Any]) -> None: - self.fields = fields_config - - def populate( # pylint: disable=too-many-branches - self, jenv: Environment, params: Dict[str, Any] - ) -> Dict[str, str]: - """Populate a dict based on the fields definition and provided vars. - """ - ret: Dict[str, str] = {} - - for key, def_ in self.fields.items(): - from_key, to_key = key, key - - if isinstance(def_, bool): - required = def_ - value = params.get(from_key) - if value is None and required: - raise KeyError(from_key) - remove_if_empty = False - elif isinstance(def_, str): - # is a template - template: Optional[str] = def_ - tmplt = jenv.from_string(cast(str, template)) - value = tmplt.render(**params) - remove_if_empty = False - elif isinstance(def_, dict): - template = def_.get("template") - remove_if_empty = def_["removeIfEmpty"] - to_key = def_.get("toKey") or to_key - from_key = def_.get("fromKey") or from_key - - if template is None: - required = def_["required"] - value = params.get(from_key) - if value is None and required: - raise KeyError(from_key) - else: - tmplt = jenv.from_string(template) - try: - value = tmplt.render(**params) - except UndefinedError: - value = "" # This empty string will be removed if `remove_if_empty` is True - else: - raise UnreachableError() - - if value is not None: - str_value = str(value) - - if not (remove_if_empty and not str_value): - if to_key in ret: - print(f"Param {key} conflicting with {to_key}", file=stderr) - ret[to_key] = str_value - continue - return ret - - -class Orient(Enum): - """Different types of table orientations - ref: (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_json.html). - Currently, DataConnector supports two different types of orientaions: - - 1. Split, which is column store. - 2. Records, which is row store. - - Details can be found in the pandas page. - """ - - Split = "split" - Records = "records" diff --git a/poetry.lock b/poetry.lock index 202fb941f..ee2133f3e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -43,7 +43,7 @@ version = "1.4.4" [[package]] category = "dev" description = "Disable App Nap on OS X 10.9" -marker = "sys_platform == \"darwin\"" +marker = "sys_platform == \"darwin\" or platform_system == \"Darwin\"" name = "appnope" optional = false python-versions = "*" @@ -74,6 +74,14 @@ wrapt = ">=1.11.0,<1.12.0" python = "<3.8" version = ">=1.4.0,<1.5" +[[package]] +category = "dev" +description = "Async generators and context managers for Python 3.5+" +name = "async-generator" +optional = false +python-versions = ">=3.5" +version = "1.10" + [[package]] category = "main" description = "Timeout context manager for asyncio programs" @@ -150,7 +158,7 @@ description = "An easy safelist-based HTML-sanitizing tool." name = "bleach" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.1.5" +version = "3.2.1" [package.dependencies] packaging = "*" @@ -260,7 +268,7 @@ description = "Code coverage measurement for Python" name = "coverage" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.2.1" +version = "5.3" [package.extras] toml = ["toml"] @@ -328,6 +336,15 @@ delayed = ["cloudpickle (>=0.2.2)", "toolz (>=0.8.2)"] diagnostics = ["bokeh (>=1.0.0)"] distributed = ["distributed (>=2.0)"] +[[package]] +category = "main" +description = "A backport of the dataclasses module for Python 3.6" +marker = "python_version < \"3.7\"" +name = "dataclasses" +optional = false +python-versions = "*" +version = "0.6" + [[package]] category = "main" description = "Decorators for Humans" @@ -372,7 +389,7 @@ description = "Distributed scheduler for Dask" name = "distributed" optional = false python-versions = ">=3.6" -version = "2.25.0" +version = "2.27.0" [package.dependencies] click = ">=6.6" @@ -416,8 +433,24 @@ category = "main" description = "File-system specification" name = "fsspec" optional = false -python-versions = ">3.5" -version = "0.8.0" +python-versions = ">3.6" +version = "0.8.2" + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +dask = ["dask", "distributed"] +dropbox = ["dropboxdrivefs", "requests", "dropbox"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +hdfs = ["pyarrow"] +http = ["requests", "aiohttp"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] [[package]] category = "dev" @@ -493,7 +526,7 @@ marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.7.0" +version = "2.0.0" [package.dependencies] zipp = ">=0.5" @@ -502,6 +535,24 @@ zipp = ">=0.5" docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] +[[package]] +category = "dev" +description = "IPython Kernel for Jupyter" +name = "ipykernel" +optional = false +python-versions = ">=3.5" +version = "5.3.4" + +[package.dependencies] +appnope = "*" +ipython = ">=5.0.0" +jupyter-client = "*" +tornado = ">=4.2" +traitlets = ">=4.1.0" + +[package.extras] +test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose"] + [[package]] category = "dev" description = "IPython: Productive Interactive Computing" @@ -628,6 +679,24 @@ version = "*" format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"] +[[package]] +category = "dev" +description = "Jupyter protocol implementation and client libraries" +name = "jupyter-client" +optional = false +python-versions = ">=3.5" +version = "6.1.7" + +[package.dependencies] +jupyter-core = ">=4.6.0" +python-dateutil = ">=2.1" +pyzmq = ">=13" +tornado = ">=4.1" +traitlets = "*" + +[package.extras] +test = ["ipykernel", "ipython", "mock", "pytest", "pytest-asyncio", "async-generator", "pytest-timeout"] + [[package]] category = "dev" description = "Jupyter core package. A base package on which Jupyter projects rely." @@ -640,6 +709,17 @@ version = "4.6.3" pywin32 = ">=1.0" traitlets = "*" +[[package]] +category = "dev" +description = "Pygments theme using JupyterLab CSS variables" +name = "jupyterlab-pygments" +optional = false +python-versions = "*" +version = "0.1.1" + +[package.dependencies] +pygments = ">=2.4.1,<3" + [[package]] category = "main" description = "A fast implementation of the Cassowary constraint solver" @@ -707,7 +787,7 @@ description = "Python plotting package" name = "matplotlib" optional = false python-versions = ">=3.6" -version = "3.3.1" +version = "3.3.2" [package.dependencies] certifi = ">=2020.06.20" @@ -782,13 +862,33 @@ optional = false python-versions = "*" version = "0.4.3" +[[package]] +category = "dev" +description = "A client library for executing notebooks. Formally nbconvert's ExecutePreprocessor." +name = "nbclient" +optional = false +python-versions = ">=3.6" +version = "0.5.0" + +[package.dependencies] +async-generator = "*" +jupyter-client = ">=6.1.5" +nbformat = ">=5.0" +nest-asyncio = "*" +traitlets = ">=4.2" + +[package.extras] +dev = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] +sphinx = ["Sphinx (>=1.7)", "sphinx-book-theme", "mock", "moto", "myst-parser"] +test = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] + [[package]] category = "dev" description = "Converting Jupyter Notebooks" name = "nbconvert" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.6.1" +python-versions = ">=3.6" +version = "6.0.6" [package.dependencies] bleach = "*" @@ -796,19 +896,21 @@ defusedxml = "*" entrypoints = ">=0.2.2" jinja2 = ">=2.4" jupyter-core = "*" +jupyterlab-pygments = "*" mistune = ">=0.8.1,<2" +nbclient = ">=0.5.0,<0.6.0" nbformat = ">=4.4" pandocfilters = ">=1.4.1" -pygments = "*" +pygments = ">=2.4.1" testpath = "*" traitlets = ">=4.2" [package.extras] -all = ["pytest", "pytest-cov", "ipykernel", "jupyter-client (>=5.3.1)", "ipywidgets (>=7)", "pebble", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "sphinxcontrib-github-alt", "ipython", "mock"] -docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "sphinxcontrib-github-alt", "ipython", "jupyter-client (>=5.3.1)"] -execute = ["jupyter-client (>=5.3.1)"] +all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"] +docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"] serve = ["tornado (>=4.0)"] -test = ["pytest", "pytest-cov", "ipykernel", "jupyter-client (>=5.3.1)", "ipywidgets (>=7)", "pebble", "mock"] +test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)"] +webpdf = ["pyppeteer (0.2.2)"] [[package]] category = "dev" @@ -843,6 +945,14 @@ nbformat = "*" sphinx = ">=1.8" traitlets = "*" +[[package]] +category = "dev" +description = "Patch asyncio to allow nested event loops" +name = "nest-asyncio" +optional = false +python-versions = ">=3.5" +version = "1.4.0" + [[package]] category = "main" description = "Natural Language Toolkit" @@ -871,7 +981,7 @@ description = "NumPy is the fundamental package for array computing with Python. name = "numpy" optional = false python-versions = ">=3.6" -version = "1.19.1" +version = "1.19.2" [[package]] category = "main" @@ -1050,13 +1160,31 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.9.0" +[[package]] +category = "main" +description = "Data validation and settings management using python 3.6 type hinting" +name = "pydantic" +optional = false +python-versions = ">=3.6" +version = "1.6.1" + +[package.dependencies] +[package.dependencies.dataclasses] +python = "<3.7" +version = ">=0.6" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] +typing_extensions = ["typing-extensions (>=3.7.2)"] + [[package]] category = "dev" description = "Pygments is a syntax highlighting package written in Python." name = "pygments" optional = false python-versions = ">=3.5" -version = "2.6.1" +version = "2.7.1" [[package]] category = "dev" @@ -1085,8 +1213,8 @@ category = "main" description = "Persistent/Functional/Immutable data structures" name = "pyrsistent" optional = false -python-versions = "*" -version = "0.17.0" +python-versions = ">=3.5" +version = "0.17.3" [[package]] category = "dev" @@ -1165,6 +1293,14 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "5.3.1" +[[package]] +category = "dev" +description = "Python bindings for 0MQ" +name = "pyzmq" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +version = "19.0.2" + [[package]] category = "main" description = "Alternative regular expression module, to replace re." @@ -1405,6 +1541,14 @@ version = "1.1.4" lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] +[[package]] +category = "main" +description = "String case converter." +name = "stringcase" +optional = false +python-versions = "*" +version = "1.2.0" + [[package]] category = "main" description = "Traceback serialization library." @@ -1437,8 +1581,8 @@ category = "main" description = "List processing tools and functional utilities" name = "toolz" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.10.0" +python-versions = ">=3.5" +version = "0.11.0" [[package]] category = "main" @@ -1575,7 +1719,7 @@ description = "Yet another URL library" name = "yarl" optional = false python-versions = ">=3.5" -version = "1.5.1" +version = "1.6.0" [package.dependencies] idna = ">=2.0" @@ -1603,14 +1747,15 @@ marker = "python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=3.6" -version = "3.1.0" +version = "3.2.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools", "func-timeout"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -content-hash = "f51ac526101b8fc27320c76115156ecc1e2c5310a42d3b1f2430dd0e7ef85fb8" +content-hash = "126407aac2a99768b50f99fc194b1be396c78e3b2eefd987fa35c20941381559" +lock-version = "1.0" python-versions = "^3.6.1" [metadata.files] @@ -1648,6 +1793,10 @@ astroid = [ {file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"}, {file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"}, ] +async-generator = [ + {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, + {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, +] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, @@ -1673,8 +1822,8 @@ black = [ {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] bleach = [ - {file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"}, - {file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"}, + {file = "bleach-3.2.1-py2.py3-none-any.whl", hash = "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"}, + {file = "bleach-3.2.1.tar.gz", hash = "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080"}, ] bokeh = [ {file = "bokeh-2.0.2.tar.gz", hash = "sha256:d9248bdb0156797abf6d04b5eac581dcb121f5d1db7acbc13282b0609314893a"}, @@ -1711,40 +1860,40 @@ contextvars = [ {file = "contextvars-2.4.tar.gz", hash = "sha256:f38c908aaa59c14335eeea12abea5f443646216c4e29380d7bf34d2018e2c39e"}, ] coverage = [ - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"}, - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"}, - {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"}, - {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"}, - {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"}, - {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"}, - {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"}, - {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"}, - {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"}, - {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"}, - {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"}, - {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"}, - {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"}, - {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"}, - {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"}, - {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"}, - {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"}, - {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"}, - {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"}, - {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] cycler = [ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, @@ -1754,6 +1903,10 @@ dask = [ {file = "dask-2.25.0-py3-none-any.whl", hash = "sha256:0663e95022b37404c4f8bd386107b215878951476edd588c6783a40201d176f3"}, {file = "dask-2.25.0.tar.gz", hash = "sha256:12ac2ee71157b8eecb89b48cd2aa0be7b1b4bb90a791d3e8d6c4002cdc9499e3"}, ] +dataclasses = [ + {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, + {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"}, +] decorator = [ {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, @@ -1765,8 +1918,8 @@ defusedxml = [ distributed = [ {file = "distributed-2.10.0-py3-none-any.whl", hash = "sha256:04342deeeb4771de9553c41de545d194b00dc88361c34ea8fca79c2210d56368"}, {file = "distributed-2.10.0.tar.gz", hash = "sha256:2f8cca741a20f776929cbad3545f2df64cf60207fb21f774ef24aad6f6589e8b"}, - {file = "distributed-2.25.0-py3-none-any.whl", hash = "sha256:e9855657b04fcb7b106f0f55487f34973f0cd50ab2cfc7259b397713d62ae19a"}, - {file = "distributed-2.25.0.tar.gz", hash = "sha256:2b3a6c9d82dcbe4b8757be2fcb3fbca731a57592e801caf06407a2a76cae2217"}, + {file = "distributed-2.27.0-py3-none-any.whl", hash = "sha256:65833e711b568a63d777d887f1551f2e78329400537c7d0c2d1ae2623c66b010"}, + {file = "distributed-2.27.0.tar.gz", hash = "sha256:782bb59799880ca4ebc0931def33dbe5d93f9f74ad3dfebb727d03ea98c2d101"}, ] docutils = [ {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, @@ -1777,8 +1930,8 @@ entrypoints = [ {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, ] fsspec = [ - {file = "fsspec-0.8.0-py3-none-any.whl", hash = "sha256:ce109f41ffe62853d5de84888f3e455c39f2a0796c05b558474c77156e19b570"}, - {file = "fsspec-0.8.0.tar.gz", hash = "sha256:176f3fc405471af0f1f1e14cffa3d53ab8906577973d068b976114433c010d9d"}, + {file = "fsspec-0.8.2-py3-none-any.whl", hash = "sha256:8ce8b237670981f3d61606e71d80b2c455a6e9a3533037639992004052e9d5e0"}, + {file = "fsspec-0.8.2.tar.gz", hash = "sha256:c08fbbb517d1c550be38ae0eba26b0b39b2b656617396ecce8c9ccd1a107c0ee"}, ] gitdb = [ {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, @@ -1818,8 +1971,12 @@ immutables = [ {file = "immutables-0.14.tar.gz", hash = "sha256:a0a1cc238b678455145bae291d8426f732f5255537ed6a5b7645949704c70a78"}, ] importlib-metadata = [ - {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, - {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, + {file = "importlib_metadata-2.0.0-py2.py3-none-any.whl", hash = "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"}, + {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"}, +] +ipykernel = [ + {file = "ipykernel-5.3.4-py3-none-any.whl", hash = "sha256:d6fbba26dba3cebd411382bc484f7bc2caa98427ae0ddb4ab37fe8bfeb5c7dd3"}, + {file = "ipykernel-5.3.4.tar.gz", hash = "sha256:9b2652af1607986a1b231c62302d070bc0534f564c393a5d9d130db9abbbe89d"}, ] ipython = [ {file = "ipython-7.13.0-py3-none-any.whl", hash = "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333"}, @@ -1853,10 +2010,18 @@ jsonschema = [ {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, ] +jupyter-client = [ + {file = "jupyter_client-6.1.7-py3-none-any.whl", hash = "sha256:c958d24d6eacb975c1acebb68ac9077da61b5f5c040f22f6849928ad7393b950"}, + {file = "jupyter_client-6.1.7.tar.gz", hash = "sha256:49e390b36fe4b4226724704ea28d9fb903f1a3601b6882ce3105221cd09377a1"}, +] jupyter-core = [ {file = "jupyter_core-4.6.3-py2.py3-none-any.whl", hash = "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21"}, {file = "jupyter_core-4.6.3.tar.gz", hash = "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e"}, ] +jupyterlab-pygments = [ + {file = "jupyterlab_pygments-0.1.1-py2.py3-none-any.whl", hash = "sha256:c9535e5999f29bff90bd0fa423717dcaf247b71fad505d66b17d3217e9021fc5"}, + {file = "jupyterlab_pygments-0.1.1.tar.gz", hash = "sha256:19a0ccde7daddec638363cd3d60b63a4f6544c9181d65253317b2fb492a797b9"}, +] kiwisolver = [ {file = "kiwisolver-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74"}, {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8"}, @@ -1969,24 +2134,24 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] matplotlib = [ - {file = "matplotlib-3.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:282f8a077a1217f9f2ac178596f27c1ae94abbc6e7b785e1b8f25e83918e9199"}, - {file = "matplotlib-3.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:83ae7261f4d5ab387be2caee29c4f499b1566f31c8ac97a0b8ab61afd9e3da92"}, - {file = "matplotlib-3.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1f9cf2b8500b833714a193cb24281153f5072d55b2e486009f1e81f0b7da3410"}, - {file = "matplotlib-3.3.1-cp36-cp36m-win32.whl", hash = "sha256:0dc15e1ad84ec06bf0c315e6c4c2cced13a21ce4c2b4955bb75097064a4b1e92"}, - {file = "matplotlib-3.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:ffbae66e2db70dc330cb3299525f97e1c0efdfc763e04e1a4e08f968c7ad21f0"}, - {file = "matplotlib-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88c6ab4a32a7447dad236b8371612aaba5c967d632ff11999e0478dd687f2c58"}, - {file = "matplotlib-3.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cc2d6b47c8fee89da982a312b54949ec0cd6a7976a8cafb5b62dea6c9883a14d"}, - {file = "matplotlib-3.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:636c6330a7dcb18bac114dbeaff314fbbb0c11682f9a9601de69a50e331d18d7"}, - {file = "matplotlib-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:73a493e340064e8fe03207d9333b68baca30d9f0da543ae4af6b6b4f13f0fe05"}, - {file = "matplotlib-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6739b6cd9278d5cb337df0bd4400ad37bbd04c6dc7aa2c65e1e83a02bc4cc6fd"}, - {file = "matplotlib-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79f0c4730ad422ecb6bda814c9a9b375df36d6bd5a49eaa14e92e5f5e3e95ac3"}, - {file = "matplotlib-3.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e4d6d3afc454b4afc0d9d0ed52a8fa40a1b0d8f33c8e143e49a5833a7e32266b"}, - {file = "matplotlib-3.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:96a5e667308dbf45670370d9dffb974e73b15bac0df0b5f3fb0b0ac7a572290e"}, - {file = "matplotlib-3.3.1-cp38-cp38-win32.whl", hash = "sha256:bd8fceaa3494b531d43b6206966ba15705638137fc2dc5da5ee560cf9476867b"}, - {file = "matplotlib-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:1507c2a8e4662f6fa1d3ecc760782b158df8a3244ecc21c1d8dbb1cd0b3f872e"}, - {file = "matplotlib-3.3.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c3619ec2a5ead430a4536ebf8c77ea55d8ce36418919f831d35bc657ed5f27e"}, - {file = "matplotlib-3.3.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:9703bc00a94a94c4e94b2ea0fbfbc9d2bb21159733134639fd931b6606c5c47e"}, - {file = "matplotlib-3.3.1.tar.gz", hash = "sha256:87f53bcce90772f942c2db56736788b39332d552461a5cb13f05ff45c1680f0e"}, + {file = "matplotlib-3.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:27f9de4784ae6fb97679556c5542cf36c0751dccb4d6407f7c62517fa2078868"}, + {file = "matplotlib-3.3.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:06866c138d81a593b535d037b2727bec9b0818cadfe6a81f6ec5715b8dd38a89"}, + {file = "matplotlib-3.3.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5ccecb5f78b51b885f0028b646786889f49c54883e554fca41a2a05998063f23"}, + {file = "matplotlib-3.3.2-cp36-cp36m-win32.whl", hash = "sha256:69cf76d673682140f46c6cb5e073332c1f1b2853c748dc1cb04f7d00023567f7"}, + {file = "matplotlib-3.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:371518c769d84af8ec9b7dcb871ac44f7a67ef126dd3a15c88c25458e6b6d205"}, + {file = "matplotlib-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:793e061054662aa27acaff9201cdd510a698541c6e8659eeceb31d66c16facc6"}, + {file = "matplotlib-3.3.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16b241c3d17be786966495229714de37de04472da472277869b8d5b456a8df00"}, + {file = "matplotlib-3.3.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fb0409754b26f48045bacd6818e44e38ca9338089f8ba689e2f9344ff2847c7"}, + {file = "matplotlib-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:548cfe81476dbac44db96e9c0b074b6fb333b4d1f12b1ae68dbed47e45166384"}, + {file = "matplotlib-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f0268613073df055bcc6a490de733012f2cf4fe191c1adb74e41cec8add1a165"}, + {file = "matplotlib-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:57be9e21073fc367237b03ecac0d9e4b8ddbe38e86ec4a316857d8d93ac9286c"}, + {file = "matplotlib-3.3.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:be2f0ec62e0939a9dcfd3638c140c5a74fc929ee3fd1f31408ab8633db6e1523"}, + {file = "matplotlib-3.3.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c5d0c2ae3e3ed4e9f46b7c03b40d443601012ffe8eb8dfbb2bd6b2d00509f797"}, + {file = "matplotlib-3.3.2-cp38-cp38-win32.whl", hash = "sha256:a522de31e07ed7d6f954cda3fbd5ca4b8edbfc592a821a7b00291be6f843292e"}, + {file = "matplotlib-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8bc1d3284dee001f41ec98f59675f4d723683e1cc082830b440b5f081d8e0ade"}, + {file = "matplotlib-3.3.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:799c421bc245a0749c1515b6dea6dc02db0a8c1f42446a0f03b3b82a60a900dc"}, + {file = "matplotlib-3.3.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2f5eefc17dc2a71318d5a3496313be5c351c0731e8c4c6182c9ac3782cfc4076"}, + {file = "matplotlib-3.3.2.tar.gz", hash = "sha256:3d2edbf59367f03cd9daf42939ca06383a7d7803e3993eb5ff1bee8e8a3fbb6b"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2059,9 +2224,13 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +nbclient = [ + {file = "nbclient-0.5.0-py3-none-any.whl", hash = "sha256:8a6e27ff581cee50895f44c41936ce02369674e85e2ad58643d8d4a6c36771b0"}, + {file = "nbclient-0.5.0.tar.gz", hash = "sha256:8ad52d27ba144fca1402db014857e53c5a864a2f407be66ca9d74c3a56d6591d"}, +] nbconvert = [ - {file = "nbconvert-5.6.1-py2.py3-none-any.whl", hash = "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"}, - {file = "nbconvert-5.6.1.tar.gz", hash = "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523"}, + {file = "nbconvert-6.0.6-py3-none-any.whl", hash = "sha256:d8549f62e739a4d51f275c2932b1783ee5039dde07a2b71de70c0296a42c8394"}, + {file = "nbconvert-6.0.6.tar.gz", hash = "sha256:68335477288aab8a9b9ec03002dce59b4eb1ca967116741ec218a4e78c129efd"}, ] nbformat = [ {file = "nbformat-5.0.7-py3-none-any.whl", hash = "sha256:ea55c9b817855e2dfcd3f66d74857342612a60b1f09653440f4a5845e6e3523f"}, @@ -2071,36 +2240,40 @@ nbsphinx = [ {file = "nbsphinx-0.7.1-py3-none-any.whl", hash = "sha256:560b23ff8468643b49e19293c154c93c6ee7090786922731e1c391bd566aac86"}, {file = "nbsphinx-0.7.1.tar.gz", hash = "sha256:f50bd750e4ee3a4e4c3cf571155eab413dc87d581c1380021e7623205b5fa648"}, ] +nest-asyncio = [ + {file = "nest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:ea51120725212ef02e5870dd77fc67ba7343fc945e3b9a7ff93384436e043b6a"}, + {file = "nest_asyncio-1.4.0.tar.gz", hash = "sha256:5773054bbc14579b000236f85bc01ecced7ffd045ec8ca4a9809371ec65a59c8"}, +] nltk = [ {file = "nltk-3.5.zip", hash = "sha256:845365449cd8c5f9731f7cb9f8bd6fd0767553b9d53af9eb1b3abf7700936b35"}, ] numpy = [ - {file = "numpy-1.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1cca51512299841bf69add3b75361779962f9cee7d9ee3bb446d5982e925b69"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c9591886fc9cbe5532d5df85cb8e0cc3b44ba8ce4367bd4cf1b93dc19713da72"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cf1347450c0b7644ea142712619533553f02ef23f92f781312f6a3553d031fc7"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed8a311493cf5480a2ebc597d1e177231984c818a86875126cfd004241a73c3e"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3673c8b2b29077f1b7b3a848794f8e11f401ba0b71c49fbd26fb40b71788b132"}, - {file = "numpy-1.19.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:56ef7f56470c24bb67fb43dae442e946a6ce172f97c69f8d067ff8550cf782ff"}, - {file = "numpy-1.19.1-cp36-cp36m-win32.whl", hash = "sha256:aaf42a04b472d12515debc621c31cf16c215e332242e7a9f56403d814c744624"}, - {file = "numpy-1.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:082f8d4dd69b6b688f64f509b91d482362124986d98dc7dc5f5e9f9b9c3bb983"}, - {file = "numpy-1.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e4f6d3c53911a9d103d8ec9518190e52a8b945bab021745af4939cfc7c0d4a9e"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b6885c12784a27e957294b60f97e8b5b4174c7504665333c5e94fbf41ae5d6a"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1bc0145999e8cb8aed9d4e65dd8b139adf1919e521177f198529687dbf613065"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:5a936fd51049541d86ccdeef2833cc89a18e4d3808fe58a8abeb802665c5af93"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc"}, - {file = "numpy-1.19.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b9792b0ac0130b277536ab8944e7b754c69560dac0415dd4b2dbd16b902c8954"}, - {file = "numpy-1.19.1-cp37-cp37m-win32.whl", hash = "sha256:b12e639378c741add21fbffd16ba5ad25c0a1a17cf2b6fe4288feeb65144f35b"}, - {file = "numpy-1.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8343bf67c72e09cfabfab55ad4a43ce3f6bf6e6ced7acf70f45ded9ebb425055"}, - {file = "numpy-1.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:667c07063940e934287993366ad5f56766bc009017b4a0fe91dbd07960d0aba7"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:480fdd4dbda4dd6b638d3863da3be82873bba6d32d1fc12ea1b8486ac7b8d129"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:935c27ae2760c21cd7354402546f6be21d3d0c806fffe967f745d5f2de5005a7"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:309cbcfaa103fc9a33ec16d2d62569d541b79f828c382556ff072442226d1968"}, - {file = "numpy-1.19.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7ed448ff4eaffeb01094959b19cbaf998ecdee9ef9932381420d514e446601cd"}, - {file = "numpy-1.19.1-cp38-cp38-win32.whl", hash = "sha256:de8b4a9b56255797cbddb93281ed92acbc510fb7b15df3f01bd28f46ebc4edae"}, - {file = "numpy-1.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:92feb989b47f83ebef246adabc7ff3b9a59ac30601c3f6819f8913458610bdcc"}, - {file = "numpy-1.19.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:e1b1dc0372f530f26a03578ac75d5e51b3868b9b76cd2facba4c9ee0eb252ab1"}, - {file = "numpy-1.19.1.zip", hash = "sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491"}, + {file = "numpy-1.19.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b594f76771bc7fc8a044c5ba303427ee67c17a09b36e1fa32bde82f5c419d17a"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e6ddbdc5113628f15de7e4911c02aed74a4ccff531842c583e5032f6e5a179bd"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3733640466733441295b0d6d3dcbf8e1ffa7e897d4d82903169529fd3386919a"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:4339741994c775396e1a274dba3609c69ab0f16056c1077f18979bec2a2c2e6e"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c6646314291d8f5ea900a7ea9c4261f834b5b62159ba2abe3836f4fa6705526"}, + {file = "numpy-1.19.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7118f0a9f2f617f921ec7d278d981244ba83c85eea197be7c5a4f84af80a9c3c"}, + {file = "numpy-1.19.2-cp36-cp36m-win32.whl", hash = "sha256:9a3001248b9231ed73894c773142658bab914645261275f675d86c290c37f66d"}, + {file = "numpy-1.19.2-cp36-cp36m-win_amd64.whl", hash = "sha256:967c92435f0b3ba37a4257c48b8715b76741410467e2bdb1097e8391fccfae15"}, + {file = "numpy-1.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d526fa58ae4aead839161535d59ea9565863bb0b0bdb3cc63214613fb16aced4"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:eb25c381d168daf351147713f49c626030dcff7a393d5caa62515d415a6071d8"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:62139af94728d22350a571b7c82795b9d59be77fc162414ada6c8b6a10ef5d02"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0c66da1d202c52051625e55a249da35b31f65a81cb56e4c69af0dfb8fb0125bf"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:2117536e968abb7357d34d754e3733b0d7113d4c9f1d921f21a3d96dec5ff716"}, + {file = "numpy-1.19.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54045b198aebf41bf6bf4088012777c1d11703bf74461d70cd350c0af2182e45"}, + {file = "numpy-1.19.2-cp37-cp37m-win32.whl", hash = "sha256:aba1d5daf1144b956bc87ffb87966791f5e9f3e1f6fab3d7f581db1f5b598f7a"}, + {file = "numpy-1.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:addaa551b298052c16885fc70408d3848d4e2e7352de4e7a1e13e691abc734c1"}, + {file = "numpy-1.19.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:58d66a6b3b55178a1f8a5fe98df26ace76260a70de694d99577ddeab7eaa9a9d"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:59f3d687faea7a4f7f93bd9665e5b102f32f3fa28514f15b126f099b7997203d"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cebd4f4e64cfe87f2039e4725781f6326a61f095bc77b3716502bed812b385a9"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c35a01777f81e7333bcf276b605f39c872e28295441c265cd0c860f4b40148c1"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d7ac33585e1f09e7345aa902c281bd777fdb792432d27fca857f39b70e5dd31c"}, + {file = "numpy-1.19.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:04c7d4ebc5ff93d9822075ddb1751ff392a4375e5885299445fcebf877f179d5"}, + {file = "numpy-1.19.2-cp38-cp38-win32.whl", hash = "sha256:51ee93e1fac3fe08ef54ff1c7f329db64d8a9c5557e6c8e908be9497ac76374b"}, + {file = "numpy-1.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:1669ec8e42f169ff715a904c9b2105b6640f3f2a4c4c2cb4920ae8b2785dac65"}, + {file = "numpy-1.19.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:0bfd85053d1e9f60234f28f63d4a5147ada7f432943c113a11afcf3e65d9d4c8"}, + {file = "numpy-1.19.2.zip", hash = "sha256:0d310730e1e793527065ad7dde736197b705d0e4c9999775f212b03c44a8484c"}, ] packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, @@ -2214,9 +2387,28 @@ py = [ {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, ] +pydantic = [ + {file = "pydantic-1.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:418b84654b60e44c0cdd5384294b0e4bc1ebf42d6e873819424f3b78b8690614"}, + {file = "pydantic-1.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4900b8820b687c9a3ed753684337979574df20e6ebe4227381d04b3c3c628f99"}, + {file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b49c86aecde15cde33835d5d6360e55f5e0067bb7143a8303bf03b872935c75b"}, + {file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2de562a456c4ecdc80cf1a8c3e70c666625f7d02d89a6174ecf63754c734592e"}, + {file = "pydantic-1.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f769141ab0abfadf3305d4fcf36660e5cf568a666dd3efab7c3d4782f70946b1"}, + {file = "pydantic-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dc946b07cf24bee4737ced0ae77e2ea6bc97489ba5a035b603bd1b40ad81f7e"}, + {file = "pydantic-1.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:36dbf6f1be212ab37b5fda07667461a9219c956181aa5570a00edfb0acdfe4a1"}, + {file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1783c1d927f9e1366e0e0609ae324039b2479a1a282a98ed6a6836c9ed02002c"}, + {file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cf3933c98cb5e808b62fae509f74f209730b180b1e3c3954ee3f7949e083a7df"}, + {file = "pydantic-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f8af9b840a9074e08c0e6dc93101de84ba95df89b267bf7151d74c553d66833b"}, + {file = "pydantic-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:40d765fa2d31d5be8e29c1794657ad46f5ee583a565c83cea56630d3ae5878b9"}, + {file = "pydantic-1.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3fa799f3cfff3e5f536cbd389368fc96a44bb30308f258c94ee76b73bd60531d"}, + {file = "pydantic-1.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6c3f162ba175678218629f446a947e3356415b6b09122dcb364e58c442c645a7"}, + {file = "pydantic-1.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:eb75dc1809875d5738df14b6566ccf9fd9c0bcde4f36b72870f318f16b9f5c20"}, + {file = "pydantic-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:530d7222a2786a97bc59ee0e0ebbe23728f82974b1f1ad9a11cd966143410633"}, + {file = "pydantic-1.6.1-py36.py37.py38-none-any.whl", hash = "sha256:b5b3489cb303d0f41ad4a7390cf606a5f2c7a94dcba20c051cd1c653694cb14d"}, + {file = "pydantic-1.6.1.tar.gz", hash = "sha256:54122a8ed6b75fe1dd80797f8251ad2063ea348a03b77218d73ea9fe19bd4e73"}, +] pygments = [ - {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, - {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, + {file = "Pygments-2.7.1-py3-none-any.whl", hash = "sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998"}, + {file = "Pygments-2.7.1.tar.gz", hash = "sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"}, ] pylint = [ {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, @@ -2227,7 +2419,7 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyrsistent = [ - {file = "pyrsistent-0.17.0.tar.gz", hash = "sha256:8690178e75460d3afe32d6222feed1ca1fdd4ef7317f6d8b42147bcce5f9ee75"}, + {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] pytest = [ {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, @@ -2272,6 +2464,36 @@ pyyaml = [ {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] +pyzmq = [ + {file = "pyzmq-19.0.2-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:59f1e54627483dcf61c663941d94c4af9bf4163aec334171686cdaee67974fe5"}, + {file = "pyzmq-19.0.2-cp27-cp27m-win32.whl", hash = "sha256:c36ffe1e5aa35a1af6a96640d723d0d211c5f48841735c2aa8d034204e87eb87"}, + {file = "pyzmq-19.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:0a422fc290d03958899743db091f8154958410fc76ce7ee0ceb66150f72c2c97"}, + {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c20dd60b9428f532bc59f2ef6d3b1029a28fc790d408af82f871a7db03e722ff"}, + {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d46fb17f5693244de83e434648b3dbb4f4b0fec88415d6cbab1c1452b6f2ae17"}, + {file = "pyzmq-19.0.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:f1a25a61495b6f7bb986accc5b597a3541d9bd3ef0016f50be16dbb32025b302"}, + {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ab0d01148d13854de716786ca73701012e07dff4dfbbd68c4e06d8888743526e"}, + {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:720d2b6083498a9281eaee3f2927486e9fe02cd16d13a844f2e95217f243efea"}, + {file = "pyzmq-19.0.2-cp35-cp35m-win32.whl", hash = "sha256:29d51279060d0a70f551663bc592418bcad7f4be4eea7b324f6dd81de05cb4c1"}, + {file = "pyzmq-19.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:5120c64646e75f6db20cc16b9a94203926ead5d633de9feba4f137004241221d"}, + {file = "pyzmq-19.0.2-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:8a6ada5a3f719bf46a04ba38595073df8d6b067316c011180102ba2a1925f5b5"}, + {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fa411b1d8f371d3a49d31b0789eb6da2537dadbb2aef74a43aa99a78195c3f76"}, + {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:00dca814469436455399660247d74045172955459c0bd49b54a540ce4d652185"}, + {file = "pyzmq-19.0.2-cp36-cp36m-win32.whl", hash = "sha256:046b92e860914e39612e84fa760fc3f16054d268c11e0e25dcb011fb1bc6a075"}, + {file = "pyzmq-19.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99cc0e339a731c6a34109e5c4072aaa06d8e32c0b93dc2c2d90345dd45fa196c"}, + {file = "pyzmq-19.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e36f12f503511d72d9bdfae11cadbadca22ff632ff67c1b5459f69756a029c19"}, + {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c40fbb2b9933369e994b837ee72193d6a4c35dfb9a7c573257ef7ff28961272c"}, + {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5d9fc809aa8d636e757e4ced2302569d6e60e9b9c26114a83f0d9d6519c40493"}, + {file = "pyzmq-19.0.2-cp37-cp37m-win32.whl", hash = "sha256:3fa6debf4bf9412e59353defad1f8035a1e68b66095a94ead8f7a61ae90b2675"}, + {file = "pyzmq-19.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:73483a2caaa0264ac717af33d6fb3f143d8379e60a422730ee8d010526ce1913"}, + {file = "pyzmq-19.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36ab114021c0cab1a423fe6689355e8f813979f2c750968833b318c1fa10a0fd"}, + {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8b66b94fe6243d2d1d89bca336b2424399aac57932858b9a30309803ffc28112"}, + {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:654d3e06a4edc566b416c10293064732516cf8871a4522e0a2ba00cc2a2e600c"}, + {file = "pyzmq-19.0.2-cp38-cp38-win32.whl", hash = "sha256:276ad604bffd70992a386a84bea34883e696a6b22e7378053e5d3227321d9702"}, + {file = "pyzmq-19.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:09d24a80ccb8cbda1af6ed8eb26b005b6743e58e9290566d2a6841f4e31fa8e0"}, + {file = "pyzmq-19.0.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:c1a31cd42905b405530e92bdb70a8a56f048c8a371728b8acf9d746ecd4482c0"}, + {file = "pyzmq-19.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a7e7f930039ee0c4c26e4dfee015f20bd6919cd8b97c9cd7afbde2923a5167b6"}, + {file = "pyzmq-19.0.2.tar.gz", hash = "sha256:296540a065c8c21b26d63e3cea2d1d57902373b16e4256afe46422691903a438"}, +] regex = [ {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"}, {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"}, @@ -2381,6 +2603,9 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, ] +stringcase = [ + {file = "stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"}, +] tblib = [ {file = "tblib-1.7.0-py2.py3-none-any.whl", hash = "sha256:289fa7359e580950e7d9743eab36b0691f0310fce64dee7d9c31065b8f723e23"}, {file = "tblib-1.7.0.tar.gz", hash = "sha256:059bd77306ea7b419d4f76016aef6d7027cc8a0785579b5aad198803435f882c"}, @@ -2394,7 +2619,8 @@ toml = [ {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, ] toolz = [ - {file = "toolz-0.10.0.tar.gz", hash = "sha256:08fdd5ef7c96480ad11c12d472de21acd32359996f69a5259299b540feba4560"}, + {file = "toolz-0.11.0-py3-none-any.whl", hash = "sha256:39e5ec7e9cbdb63a6aa2ad8a257a4ba6572424ce776fa543a3a151d8479dce5f"}, + {file = "toolz-0.11.0.tar.gz", hash = "sha256:39701a4c1ed9498e9d4847bb52181807b9f90c598ee9dd5ed27b0f4c007a595a"}, ] tornado = [ {file = "tornado-5.0.2-cp35-cp35m-win32.whl", hash = "sha256:88ce0282cce70df9045e515f578c78f1ebc35dcabe1d70f800c3583ebda7f5f5"}, @@ -2484,29 +2710,29 @@ wrapt = [ {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, ] yarl = [ - {file = "yarl-1.5.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb"}, - {file = "yarl-1.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593"}, - {file = "yarl-1.5.1-cp35-cp35m-win32.whl", hash = "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409"}, - {file = "yarl-1.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317"}, - {file = "yarl-1.5.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511"}, - {file = "yarl-1.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e"}, - {file = "yarl-1.5.1-cp36-cp36m-win32.whl", hash = "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f"}, - {file = "yarl-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2"}, - {file = "yarl-1.5.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a"}, - {file = "yarl-1.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8"}, - {file = "yarl-1.5.1-cp37-cp37m-win32.whl", hash = "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8"}, - {file = "yarl-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d"}, - {file = "yarl-1.5.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02"}, - {file = "yarl-1.5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a"}, - {file = "yarl-1.5.1-cp38-cp38-win32.whl", hash = "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6"}, - {file = "yarl-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692"}, - {file = "yarl-1.5.1.tar.gz", hash = "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6"}, + {file = "yarl-1.6.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:db9eb8307219d7e09b33bcb43287222ef35cbcf1586ba9472b0a4b833666ada1"}, + {file = "yarl-1.6.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e31fef4e7b68184545c3d68baec7074532e077bd1906b040ecfba659737df188"}, + {file = "yarl-1.6.0-cp35-cp35m-win32.whl", hash = "sha256:5d84cc36981eb5a8533be79d6c43454c8e6a39ee3118ceaadbd3c029ab2ee580"}, + {file = "yarl-1.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:5e447e7f3780f44f890360ea973418025e8c0cdcd7d6a1b221d952600fd945dc"}, + {file = "yarl-1.6.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6f6898429ec3c4cfbef12907047136fd7b9e81a6ee9f105b45505e633427330a"}, + {file = "yarl-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d088ea9319e49273f25b1c96a3763bf19a882cff774d1792ae6fba34bd40550a"}, + {file = "yarl-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b7c199d2cbaf892ba0f91ed36d12ff41ecd0dde46cbf64ff4bfe997a3ebc925e"}, + {file = "yarl-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:67c5ea0970da882eaf9efcf65b66792557c526f8e55f752194eff8ec722c75c2"}, + {file = "yarl-1.6.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:04a54f126a0732af75e5edc9addeaa2113e2ca7c6fce8974a63549a70a25e50e"}, + {file = "yarl-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcbe419805c9b20db9a51d33b942feddbf6e7fb468cb20686fd7089d4164c12a"}, + {file = "yarl-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:c604998ab8115db802cc55cb1b91619b2831a6128a62ca7eea577fc8ea4d3131"}, + {file = "yarl-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c22607421f49c0cb6ff3ed593a49b6a99c6ffdeaaa6c944cdda83c2393c8864d"}, + {file = "yarl-1.6.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:7ce35944e8e61927a8f4eb78f5bc5d1e6da6d40eadd77e3f79d4e9399e263921"}, + {file = "yarl-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c15d71a640fb1f8e98a1423f9c64d7f1f6a3a168f803042eaf3a5b5022fde0c1"}, + {file = "yarl-1.6.0-cp38-cp38-win32.whl", hash = "sha256:3cc860d72ed989f3b1f3abbd6ecf38e412de722fb38b8f1b1a086315cf0d69c5"}, + {file = "yarl-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:e32f0fb443afcfe7f01f95172b66f279938fbc6bdaebe294b0ff6747fb6db020"}, + {file = "yarl-1.6.0.tar.gz", hash = "sha256:61d3ea3c175fe45f1498af868879c6ffeb989d4143ac542163c45538ba5ec21b"}, ] zict = [ {file = "zict-2.0.0-py3-none-any.whl", hash = "sha256:26aa1adda8250a78dfc6a78d200bfb2ea43a34752cf58980bca75dde0ba0c6e9"}, {file = "zict-2.0.0.tar.gz", hash = "sha256:8e2969797627c8a663575c2fc6fcb53a05e37cdb83ee65f341fc6e0c3d0ced16"}, ] zipp = [ - {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, - {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, + {file = "zipp-3.2.0-py3-none-any.whl", hash = "sha256:43f4fa8d8bb313e65d8323a3952ef8756bf40f9a5c3ea7334be23ee4ec8278b6"}, + {file = "zipp-3.2.0.tar.gz", hash = "sha256:b52f22895f4cfce194bc8172f3819ee8de7540aa6d873535a8668b730b8b411f"}, ] diff --git a/pyproject.toml b/pyproject.toml index c0f542a77..9183f3d50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,8 @@ tornado = "5.0.2" jsonpath-ng = "~1.5" aiohttp = "~3.6" bottleneck = "^1.3.2" +stringcase = "^1.2.0" +pydantic = "^1.6.1" [tool.poetry.dev-dependencies] pylint = "~2.4" @@ -74,6 +76,7 @@ ipython = "~7.13" rope = "^0.16.0" seaborn = "^0.10.1" gitpython = "~3.1" +ipykernel = "^5.3.4" [tool.black] line-length = 88