diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 93de23c9..c1a8652b 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -31,7 +31,8 @@ jobs: # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # Black declares E203 is not PEP8 compliant: https://github.com/psf/black/issues/315#issuecomment-395457972 + flake8 . --count --ignore=E203 --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | poetry run pytest tests diff --git a/hunter/config.py b/hunter/config.py index 849b9687..e1a96c55 100644 --- a/hunter/config.py +++ b/hunter/config.py @@ -1,9 +1,9 @@ import os from dataclasses import dataclass -from typing import Dict, List +from pathlib import Path +from typing import Dict, List, Optional from expandvars import expandvars -from pathlib import Path from ruamel.yaml import YAML from hunter.grafana import GrafanaConfig @@ -15,8 +15,8 @@ @dataclass class Config: - graphite: GraphiteConfig - grafana: GrafanaConfig + graphite: Optional[GraphiteConfig] + grafana: Optional[GrafanaConfig] tests: Dict[str, TestConfig] test_groups: Dict[str, List[TestConfig]] slack: SlackConfig @@ -30,14 +30,14 @@ class ConfigError(Exception): def load_templates(config: Dict) -> Dict[str, Dict]: templates = config.get("templates", {}) if not isinstance(templates, Dict): - raise ConfigError(f"Property `templates` is not a dictionary") + raise ConfigError("Property `templates` is not a dictionary") return templates def load_tests(config: Dict, templates: Dict) -> Dict[str, TestConfig]: tests = config.get("tests", {}) if not isinstance(tests, Dict): - raise ConfigError(f"Property `tests` is not a dictionary") + raise ConfigError("Property `tests` is not a dictionary") result = {} for (test_name, test_config) in tests.items(): @@ -57,7 +57,7 @@ def load_tests(config: Dict, templates: Dict) -> Dict[str, TestConfig]: def load_test_groups(config: Dict, tests: Dict[str, TestConfig]) -> Dict[str, List[TestConfig]]: groups = config.get("test_groups", {}) if not isinstance(groups, Dict): - raise ConfigError(f"Property `test_groups` is not a dictionary") + raise ConfigError("Property `test_groups` is not a dictionary") result = {} for (group_name, test_names) in groups.items(): @@ -85,13 +85,22 @@ def load_config_from(config_file: Path) -> Config: if Grafana configs not explicitly set in yaml file, default to same as Graphite server at port 3000 """ - if config["graphite"]["url"] is None: - raise ValueError("graphite.url") - if config.get("grafana") is None: - config["gafana"] = {} - config["grafana"]["url"] = f"{config['graphite']['url'].strip('/')}:3000/" - config["grafana"]["user"] = os.environ.get("GRAFANA_USER", "admin") - config["grafana"]["password"] = os.environ.get("GRAFANA_PASSWORD", "admin") + graphite_config = None + grafana_config = None + if "graphite" in config: + if "url" not in config["graphite"]: + raise ValueError("graphite.url") + graphite_config = GraphiteConfig(url=config["graphite"]["url"]) + if config.get("grafana") is None: + config["grafana"] = {} + config["grafana"]["url"] = f"{config['graphite']['url'].strip('/')}:3000/" + config["grafana"]["user"] = os.environ.get("GRAFANA_USER", "admin") + config["grafana"]["password"] = os.environ.get("GRAFANA_PASSWORD", "admin") + grafana_config = GrafanaConfig( + url=config["grafana"]["url"], + user=config["grafana"]["user"], + password=config["grafana"]["password"], + ) slack_config = None if config.get("slack") is not None: @@ -106,12 +115,8 @@ def load_config_from(config_file: Path) -> Config: groups = load_test_groups(config, tests) return Config( - graphite=GraphiteConfig(url=config["graphite"]["url"]), - grafana=GrafanaConfig( - url=config["grafana"]["url"], - user=config["grafana"]["user"], - password=config["grafana"]["password"], - ), + graphite=graphite_config, + grafana=grafana_config, slack=slack_config, tests=tests, test_groups=groups, diff --git a/hunter/csv_options.py b/hunter/csv_options.py index 93125ee7..c26782e6 100644 --- a/hunter/csv_options.py +++ b/hunter/csv_options.py @@ -1,7 +1,6 @@ import enum from dataclasses import dataclass -from typing import Optional @dataclass diff --git a/hunter/data_selector.py b/hunter/data_selector.py index f932d878..ca6c5080 100644 --- a/hunter/data_selector.py +++ b/hunter/data_selector.py @@ -1,7 +1,7 @@ import sys from dataclasses import dataclass -from typing import List, Optional from datetime import datetime, timedelta +from typing import List, Optional import pytz diff --git a/hunter/grafana.py b/hunter/grafana.py index 1abe61dd..a643c9aa 100644 --- a/hunter/grafana.py +++ b/hunter/grafana.py @@ -1,13 +1,10 @@ -import re +from dataclasses import asdict, dataclass from datetime import datetime +from typing import List, Optional import requests - -from dataclasses import asdict, dataclass - from pytz import UTC from requests.exceptions import HTTPError -from typing import List, Optional @dataclass diff --git a/hunter/graphite.py b/hunter/graphite.py index f417fe08..2ffb33bd 100644 --- a/hunter/graphite.py +++ b/hunter/graphite.py @@ -1,10 +1,9 @@ import ast import json import urllib.request - from dataclasses import dataclass from datetime import datetime -from logging import info, warning +from logging import info from typing import Dict, List, Optional, Iterable from hunter.data_selector import DataSelector diff --git a/hunter/importer.py b/hunter/importer.py index 5fe25d4d..7caa71bd 100644 --- a/hunter/importer.py +++ b/hunter/importer.py @@ -1,17 +1,22 @@ import csv from collections import OrderedDict - +from contextlib import contextmanager from dataclasses import dataclass from datetime import datetime, timedelta from pathlib import Path from typing import List, Optional, Dict from hunter.config import Config -from hunter.csv_options import CsvColumnType, CsvOptions from hunter.data_selector import DataSelector from hunter.graphite import DataPoint, Graphite, GraphiteError from hunter.series import Series, Metric -from hunter.test_config import CsvTestConfig, TestConfig, GraphiteTestConfig, CsvMetric +from hunter.test_config import ( + CsvTestConfig, + TestConfig, + GraphiteTestConfig, + CsvMetric, + HistoStatTestConfig, +) from hunter.util import ( merge_sorted, parse_datetime, @@ -19,8 +24,6 @@ format_timestamp, resolution, round, - is_float, - is_datetime, ) @@ -107,7 +110,7 @@ def fetch_data(self, test: TestConfig, selector: DataSelector = DataSelector()) raise DataImportError(f"No timeseries found in Graphite for test {test.name}.") times = [[x.time for x in series.points] for series in graphite_result] - time: List[int] = merge_sorted(times)[-selector.last_n_points:] + time: List[int] = merge_sorted(times)[-selector.last_n_points :] def column(series: List[DataPoint]) -> List[float]: value_by_time = dict([(x.time, x.value) for x in series]) @@ -281,15 +284,15 @@ def fetch_data(self, test_conf: TestConfig, selector: DataSelector = DataSelecto metrics = {m.name: Metric(m.direction, m.scale) for m in metrics.values()} # Leave last n points: - time = time[-selector.last_n_points:] + time = time[-selector.last_n_points :] tmp = data data = {} for k, v in tmp.items(): - data[k] = v[-selector.last_n_points:] + data[k] = v[-selector.last_n_points :] tmp = attributes attributes = {} for k, v in tmp.items(): - attributes[k] = v[-selector.last_n_points:] + attributes[k] = v[-selector.last_n_points :] return Series( test_conf.name, @@ -314,15 +317,131 @@ def fetch_all_metric_names(self, test_conf: CsvTestConfig) -> List[str]: return [m for m in test_conf.metrics.keys()] +class HistoStatImporter(Importer): + + __TAG_METRICS = { + "count": {"direction": 1, "scale": "1", "col": 3}, + "min": {"direction": -1, "scale": "1.0e-6", "col": 4}, + "p25": {"direction": -1, "scale": "1.0e-6", "col": 5}, + "p50": {"direction": -1, "scale": "1.0e-6", "col": 6}, + "p75": {"direction": -1, "scale": "1.0e-6", "col": 7}, + "p90": {"direction": -1, "scale": "1.0e-6", "col": 8}, + "p95": {"direction": -1, "scale": "1.0e-6", "col": 9}, + "p98": {"direction": -1, "scale": "1.0e-6", "col": 10}, + "p99": {"direction": -1, "scale": "1.0e-6", "col": 11}, + "p999": {"direction": -1, "scale": "1.0e-6", "col": 12}, + "p9999": {"direction": -1, "scale": "1.0e-6", "col": 13}, + "max": {"direction": -1, "scale": "1.0e-6", "col": 14}, + } + + @contextmanager + def __csv_reader(self, test: HistoStatTestConfig): + with open(Path(test.file), newline="") as csv_file: + yield csv.reader(csv_file) + + @staticmethod + def __parse_tag(tag: str): + return tag.split("=")[1] + + def __get_tags(self, test: HistoStatTestConfig) -> List[str]: + tags = set() + with self.__csv_reader(test) as reader: + for row in reader: + if row[0].startswith("#"): + continue + tag = self.__parse_tag(row[0]) + if tag in tags: + break + tags.add(tag) + return list(tags) + + @staticmethod + def __metric_from_components(tag, tag_metric): + return f"{tag}.{tag_metric}" + + @staticmethod + def __convert_floating_point_millisecond(fpm: str) -> int: # to epoch seconds + return int(float(fpm) * 1000) // 1000 + + def fetch_data( + self, test: HistoStatTestConfig, selector: DataSelector = DataSelector() + ) -> Series: + def selected(metric_name): + return metric_name in selector.metrics if selector.metrics is not None else True + + metrics = {} + tag_count = 0 + for tag in self.__get_tags(test): + tag_count += 1 + for tag_metric, attrs in self.__TAG_METRICS.items(): + if selected(self.__metric_from_components(tag, tag_metric)): + metrics[self.__metric_from_components(tag, tag_metric)] = Metric( + attrs["direction"], attrs["scale"] + ) + + data = {k: [] for k in metrics.keys()} + time = [] + with self.__csv_reader(test) as reader: + start_time = None + for row in reader: + if not row[0].startswith("#"): + break + if "StartTime" in row[0]: + parts = row[0].split(" ") + start_time = self.__convert_floating_point_millisecond(parts[1]) + + if not start_time: + raise DataImportError("No Start Time specified in HistoStat CSV comment") + + # Last iteration of row is the first non-comment row. Parse it now. + tag_interval = 0 + while row: + if tag_interval % tag_count == 0: + # Introduces a slight inaccuracy - each tag can report its interval start time + # with some millisecond difference. Choosing a single tag interval allows us + # to maintain the 'indexed by a single time variable' contract required by + # Series, but the time reported for almost all metrics will be _slightly_ off. + time.append(self.__convert_floating_point_millisecond(row[1]) + start_time) + tag_interval += 1 + tag = self.__parse_tag(row[0]) + for tag_metric, attrs in self.__TAG_METRICS.items(): + if selected(self.__metric_from_components(tag, tag_metric)): + data[self.__metric_from_components(tag, tag_metric)].append( + float(row[attrs["col"]]) + ) + try: + row = next(reader) + except StopIteration: + row = None + + # Leave last n points: + time = time[-selector.last_n_points :] + tmp = data + data = {} + for k, v in tmp.items(): + data[k] = v[-selector.last_n_points :] + + return Series(test.name, None, time, metrics, data, dict()) + + def fetch_all_metric_names(self, test: HistoStatTestConfig) -> List[str]: + metric_names = [] + for tag in self.__get_tags(test): + for tag_metric in self.__TAG_METRICS.keys(): + metric_names.append(self.__metric_from_components(tag, tag_metric)) + return metric_names + + class Importers: __config: Config __csv_importer: Optional[CsvImporter] __graphite_importer: Optional[GraphiteImporter] + __histostat_importer: Optional[HistoStatImporter] def __init__(self, config: Config): self.__config = config self.__csv_importer = None self.__graphite_importer = None + self.__histostat_importer = None def csv_importer(self) -> CsvImporter: if self.__csv_importer is None: @@ -334,10 +453,17 @@ def graphite_importer(self) -> GraphiteImporter: self.__graphite_importer = GraphiteImporter(Graphite(self.__config.graphite)) return self.__graphite_importer + def histostat_importer(self) -> HistoStatImporter: + if self.__histostat_importer is None: + self.__histostat_importer = HistoStatImporter() + return self.__histostat_importer + def get(self, test: TestConfig) -> Importer: if isinstance(test, CsvTestConfig): return self.csv_importer() elif isinstance(test, GraphiteTestConfig): return self.graphite_importer() + elif isinstance(test, HistoStatTestConfig): + return self.histostat_importer() else: raise ValueError(f"Unsupported test type {type(test)}") diff --git a/hunter/main.py b/hunter/main.py index db4b58f9..b663478e 100644 --- a/hunter/main.py +++ b/hunter/main.py @@ -4,24 +4,21 @@ import sys from dataclasses import dataclass from datetime import datetime, timedelta -from slack_sdk import WebClient from typing import Dict, Optional, List import pytz +from slack_sdk import WebClient from hunter import config from hunter.attributes import get_back_links from hunter.config import ConfigError, Config from hunter.data_selector import DataSelector - from hunter.grafana import GrafanaError, Grafana, Annotation from hunter.graphite import GraphiteError from hunter.importer import DataImportError, Importers -from hunter.report import Report +from hunter.report import Report, ReportType from hunter.series import ( AnalysisOptions, - ChangePointGroup, - SeriesComparison, compare, AnalyzedSeries, ) @@ -91,15 +88,19 @@ def list_metrics(self, test: TestConfig): print(metric_name) def analyze( - self, test: TestConfig, selector: DataSelector, options: AnalysisOptions + self, + test: TestConfig, + selector: DataSelector, + options: AnalysisOptions, + report_type: ReportType, ) -> AnalyzedSeries: importer = self.__importers.get(test) series = importer.fetch_data(test, selector) analyzed_series = series.analyze(options) change_points = analyzed_series.change_points_by_time report = Report(series, change_points) - print(test.name + ":") - print(report.format_log_annotated()) + produced_report = report.produce_report(test.name, report_type) + print(produced_report) return analyzed_series def __get_grafana(self) -> Grafana: @@ -188,10 +189,10 @@ def remove_grafana_annotations(self, test: Optional[TestConfig], force: bool): if test: logging.info(f"Fetching Grafana annotations for test {test.name}...") else: - logging.info(f"Fetching Grafana annotations...") + logging.info("Fetching Grafana annotations...") tags_to_query = {"hunter", "change-point"} if test: - tags_to_query.add("test:" + test.name) + tags_to_query.add(f"test: {test.name}") annotations = grafana.fetch_annotations(None, None, list(tags_to_query)) if not annotations: logging.info("No annotations found.") @@ -378,7 +379,7 @@ def setup_data_selector_parser(parser: argparse.ArgumentParser): type=int, metavar="COUNT", dest="last_n_points", - help="the number of data points to take from the end of the series" + help="the number of data points to take from the end of the series", ) @@ -492,6 +493,14 @@ def main(): metavar="DATE", dest="cph_report_since", ) + analyze_parser.add_argument( + "--output", + help="Output format for the generated report.", + choices=list(ReportType), + dest="report_type", + default=ReportType.LOG, + type=ReportType, + ) setup_data_selector_parser(analyze_parser) setup_analysis_options_parser(analyze_parser) @@ -510,8 +519,9 @@ def main(): "--force", help="don't ask questions, just do it", dest="force", action="store_true" ) - validate_parser = subparsers.add_parser("validate", - help="validates the tests and metrics defined in the configuration") + subparsers.add_parser( + "validate", help="validates the tests and metrics defined in the configuration" + ) try: args = parser.parse_args() @@ -535,14 +545,17 @@ def main(): slack_cph_since = parse_datetime(args.cph_report_since) data_selector = data_selector_from_args(args) options = analysis_options_from_args(args) + report_type = args.report_type tests = hunter.get_tests(*args.tests) tests_analyzed_series = {test.name: None for test in tests} for test in tests: try: - analyzed_series = hunter.analyze(test, selector=data_selector, options=options) + analyzed_series = hunter.analyze( + test, selector=data_selector, options=options, report_type=report_type + ) if update_grafana_flag: if not isinstance(test, GraphiteTestConfig): - raise GrafanaError(f"Not a Graphite test") + raise GrafanaError("Not a Graphite test") hunter.update_grafana_annotations(test, analyzed_series) if slack_notification_channels: tests_analyzed_series[test.name] = analyzed_series @@ -568,9 +581,7 @@ def main(): errors = 0 for test in tests: try: - regressions = hunter.regressions( - test, selector=data_selector, options=options - ) + regressions = hunter.regressions(test, selector=data_selector, options=options) if regressions: regressing_test_count += 1 except HunterError as err: @@ -586,7 +597,7 @@ def main(): else: print(f"Regressions in {regressing_test_count} tests found") if errors > 0: - print(f"Some tests were skipped due to import / analyze errors. Consult error log.") + print("Some tests were skipped due to import / analyze errors. Consult error log.") if args.command == "remove-annotations": if args.tests: diff --git a/hunter/report.py b/hunter/report.py index 5bbb4d88..58a84b0a 100644 --- a/hunter/report.py +++ b/hunter/report.py @@ -1,12 +1,22 @@ from collections import OrderedDict +from enum import Enum, unique from typing import List from tabulate import tabulate -from hunter.series import Series, ChangePoint, ChangePointGroup +from hunter.series import Series, ChangePointGroup from hunter.util import format_timestamp, insert_multiple, remove_common_prefix +@unique +class ReportType(Enum): + LOG = "log" + JSON = "json" + + def __str__(self): + return self.value + + class Report: __series: Series __change_points: List[ChangePointGroup] @@ -19,7 +29,17 @@ def __init__(self, series: Series, change_points: List[ChangePointGroup]): def __column_widths(log: List[str]) -> List[int]: return [len(c) for c in log[1].split(None)] - def format_log(self) -> str: + def produce_report(self, test_name: str, report_type: ReportType): + if report_type == ReportType.LOG: + return self.__format_log_annotated(test_name) + elif report_type == ReportType.JSON: + return self.__format_json(test_name) + else: + from hunter.main import HunterError + + raise HunterError(f"Unknown report type: {report_type}") + + def __format_log(self) -> str: time_column = [format_timestamp(ts) for ts in self.__series.time] table = {"time": time_column, **self.__series.attributes, **self.__series.data} metrics = list(self.__series.data.keys()) @@ -30,9 +50,9 @@ def format_log(self) -> str: ) return tabulate(table, headers=headers) - def format_log_annotated(self) -> str: + def __format_log_annotated(self, test_name: str) -> str: """Returns test log with change points marked as horizontal lines""" - lines = self.format_log().split("\n") + lines = self.__format_log().split("\n") col_widths = self.__column_widths(lines) indexes = [cp.index for cp in self.__change_points] separators = [] @@ -58,3 +78,8 @@ def format_log_annotated(self) -> str: lines = lines[:2] + insert_multiple(lines[2:], separators, indexes) return "\n".join(lines) + + def __format_json(self, test_name: str) -> str: + import json + + return json.dumps({test_name: [cpg.to_json() for cpg in self.__change_points]}) diff --git a/hunter/series.py b/hunter/series.py index be885bf2..5ad0279e 100644 --- a/hunter/series.py +++ b/hunter/series.py @@ -2,7 +2,9 @@ from dataclasses import dataclass from datetime import datetime from itertools import groupby -from typing import Dict, List, Optional, Iterable, OrderedDict +from typing import Dict, List, Optional, Iterable + +import numpy as np from hunter.analysis import ( fill_missing, @@ -11,8 +13,6 @@ TTestSignificanceTester, ) -import numpy as np - @dataclass class AnalysisOptions: @@ -56,6 +56,12 @@ def backward_change_percent(self) -> float: def magnitude(self): return self.stats.change_magnitude() + def to_json(self): + return { + "metric": self.metric, + "forward_change_percent": f"{self.forward_change_percent():.0f}", + } + @dataclass class ChangePointGroup: @@ -68,6 +74,9 @@ class ChangePointGroup: prev_attributes: Dict[str, str] changes: List[ChangePoint] + def to_json(self): + return {"time": self.time, "changes": [cp.to_json() for cp in self.changes]} + class Series: """ @@ -115,7 +124,7 @@ def find_first_not_earlier_than(self, time: datetime) -> Optional[int]: return None def find_by_attribute(self, name: str, value: str) -> List[int]: - """ Returns the indexes of data points with given attribute value """ + """Returns the indexes of data points with given attribute value""" result = [] for i in range(len(self.time)): if self.attributes_at(i).get(name) == value: diff --git a/hunter/slack.py b/hunter/slack.py index a454823a..695395eb 100644 --- a/hunter/slack.py +++ b/hunter/slack.py @@ -3,8 +3,8 @@ from math import isinf from typing import Dict, List -from slack_sdk import WebClient from pytz import UTC +from slack_sdk import WebClient from hunter.data_selector import DataSelector from hunter.series import ChangePointGroup, AnalyzedSeries @@ -139,6 +139,7 @@ def __text_block(cls, type, text_type, text): def __fields_section(cls, fields_text): def field_block(text): return {"type": "mrkdwn", "text": text} + return cls.__block("section", content={"fields": [field_block(t) for t in fields_text]}) @classmethod diff --git a/hunter/test_config.py b/hunter/test_config.py index 0e6145fb..46a721bf 100644 --- a/hunter/test_config.py +++ b/hunter/test_config.py @@ -1,7 +1,6 @@ -import collections -from abc import abstractmethod +import os.path from dataclasses import dataclass -from typing import Dict, List, Set, Optional +from typing import Dict, List, Optional from hunter.csv_options import CsvOptions from hunter.util import interpolate @@ -110,6 +109,18 @@ def get_path(self, branch_name: Optional[str], metric_name: str) -> str: def fully_qualified_metric_names(self): return [f"{self.prefix}.{m.suffix}" for _, m in self.metrics.items()] + +@dataclass +class HistoStatTestConfig(TestConfig): + name: str + file: str + + def fully_qualified_metric_names(self): + from hunter.importer import HistoStatImporter + + return HistoStatImporter().fetch_all_metric_names(self) + + def create_test_config(name: str, config: Dict) -> TestConfig: """ Loads properties of a test from a dictionary read from hunter's config file @@ -122,6 +133,8 @@ def create_test_config(name: str, config: Dict) -> TestConfig: return create_csv_test_config(name, config) elif test_type == "graphite": return create_graphite_test_config(name, config) + elif test_type == "histostat": + return create_histostat_test_config(name, config) elif test_type is None: raise TestConfigError(f"Test type not set for test {name}") else: @@ -201,3 +214,15 @@ def create_graphite_test_config(name: str, test_info: Dict) -> GraphiteTestConfi annotate=test_info.get("annotate", []), metrics=metrics, ) + + +def create_histostat_test_config(name: str, test_info: Dict) -> HistoStatTestConfig: + try: + file = test_info["file"] + except KeyError as e: + raise TestConfigError(f"Configuration key not found in test {name}: {e.args[0]}") + if not os.path.exists(file): + raise TestConfigError( + f"Configuration referenced histostat file which does not exist: {file}" + ) + return HistoStatTestConfig(name, file) diff --git a/poetry.lock b/poetry.lock index d763ee93..58326467 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,11 +1,3 @@ -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "atomicwrites" version = "1.4.0" @@ -16,75 +8,92 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "backports.zoneinfo" +version = "0.2.1" +description = "Backport of the standard library zoneinfo module" +category = "main" +optional = false +python-versions = ">=3.6" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tzdata = ["tzdata"] [[package]] name = "black" -version = "20.8b1" +version = "22.3.0" description = "The uncompromising code formatter." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" -regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = "*" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "7.1.2" +version = "8.1.2" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dateparser" -version = "1.0.0" +version = "1.1.1" description = "Date parsing library designed to parse dates from HTML pages" category = "main" optional = false @@ -93,15 +102,17 @@ python-versions = ">=3.5" [package.dependencies] python-dateutil = "*" pytz = "*" -regex = "!=2019.02.19" +regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27,<2022.3.15" tzlocal = "*" [package.extras] calendars = ["convertdate", "hijri-converter", "convertdate"] +fasttext = ["fasttext"] +langdetect = ["langdetect"] [[package]] name = "decorator" -version = "5.0.7" +version = "5.1.1" description = "Decorators for Humans" category = "main" optional = false @@ -117,11 +128,11 @@ python-versions = "*" [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "iniconfig" @@ -133,7 +144,7 @@ python-versions = "*" [[package]] name = "more-itertools" -version = "8.7.0" +version = "8.12.0" description = "More routines for operating on iterables, beyond itertools" category = "main" optional = false @@ -157,61 +168,81 @@ python-versions = ">=3.6" [[package]] name = "packaging" -version = "20.9" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" -version = "0.8.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pyparsing" -version = "2.4.7" -description = "Python parsing module" +version = "3.0.8" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pystache" -version = "0.5.4" +version = "0.6.0" description = "Mustache for Python" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" + +[package.extras] +cov = ["coverage", "coverage-python-version"] +test = ["nose"] [[package]] name = "pytest" -version = "6.2.3" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -223,7 +254,7 @@ attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -232,7 +263,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "python-dateutil" -version = "2.8.1" +version = "2.8.2" description = "Extensions to the standard Python datetime module" category = "main" optional = false @@ -249,44 +280,67 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "pytz-deprecation-shim" +version = "0.1.0.post0" +description = "Shims to make deprecation of pytz easier" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +"backports.zoneinfo" = {version = "*", markers = "python_version >= \"3.6\" and python_version < \"3.9\""} +tzdata = {version = "*", markers = "python_version >= \"3.6\""} + [[package]] name = "regex" -version = "2021.4.4" +version = "2022.3.2" description = "Alternative regular expression module, to replace re." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "requests" -version = "2.25.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "ruamel.yaml" -version = "0.15.89" +version = "0.17.21" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" category = "main" optional = false -python-versions = "*" +python-versions = ">=3" + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} [package.extras] docs = ["ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] +[[package]] +name = "ruamel.yaml.clib" +version = "0.2.6" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "scipy" version = "1.6.1" @@ -315,7 +369,7 @@ typing-extensions = ">=3.7.4,<4.0.0" [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false @@ -323,15 +377,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.4.2" +version = "3.15.2" description = "The Slack API Platform SDK for Python" category = "main" optional = false python-versions = ">=3.6.0" [package.extras] -optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=8,<9)", "websocket-client (>=0.57)"] -testing = ["pytest (>=5.4,<6)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=3,<4)", "black (==20.8b1)", "psutil (>=5,<6)", "databases (>=0.3)"] +optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==2.0.1)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.1.0)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "structlog" @@ -365,49 +419,63 @@ widechars = ["wcwidth"] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "typing-extensions" -version = "3.7.4.3" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false python-versions = "*" +[[package]] +name = "tzdata" +version = "2022.1" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + [[package]] name = "tzlocal" -version = "2.1" +version = "4.2" description = "tzinfo object for the local timezone" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.dependencies] -pytz = "*" +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} +pytz-deprecation-shim = "*" +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] +test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] [[package]] name = "urllib3" -version = "1.26.4" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "validators" @@ -427,63 +495,99 @@ test = ["pytest (>=2.2.3)", "flake8 (>=2.4.0)", "isort (>=4.2.2)"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "684058ad65a4dfc344038d68e3f07d5f06313747c369547543b9e8e65acb9732" +content-hash = "bbd96ec5ca0f566795f5603ec877c05faaadf87131a847c38d2c77697b260d10" [metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +"backports.zoneinfo" = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, + {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] dateparser = [ - {file = "dateparser-1.0.0-py2.py3-none-any.whl", hash = "sha256:17202df32c7a36e773136ff353aa3767e987f8b3e27374c39fd21a30a803d6f8"}, - {file = "dateparser-1.0.0.tar.gz", hash = "sha256:159cc4e01a593706a15cd4e269a0b3345edf3aef8bf9278a57dac8adf5bf1e4a"}, + {file = "dateparser-1.1.1-py2.py3-none-any.whl", hash = "sha256:9600874312ff28a41f96ec7ccdc73be1d1c44435719da47fea3339d55ff5a628"}, + {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, ] decorator = [ - {file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"}, - {file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"}, + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] expandvars = [ {file = "expandvars-0.6.5-py2.py3-none-any.whl", hash = "sha256:1cba07c76da50c2528cf8d70c78e98a62ec3e9cd279ba80fa6b136330f4ad8d3"}, {file = "expandvars-0.6.5.tar.gz", hash = "sha256:1093a7a58ee5087c279a4cc5fd5758ad9c858c2f5bee256927b18b26a6ecc9d3"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] more-itertools = [ - {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, - {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, + {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"}, + {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -518,110 +622,158 @@ numpy = [ {file = "numpy-1.19.0.zip", hash = "sha256:76766cc80d6128750075378d3bb7812cf146415bd29b588616f72c943c00d598"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, + {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pystache = [ - {file = "pystache-0.5.4.tar.gz", hash = "sha256:f7bbc265fb957b4d6c7c042b336563179444ab313fb93a719759111eabd3b85a"}, + {file = "pystache-0.6.0.tar.gz", hash = "sha256:93bf92b2149a4c4b58d12142e2c4c6dd5c08d89e4c95afccd4b6efe2ee1d470d"}, ] pytest = [ - {file = "pytest-6.2.3-py3-none-any.whl", hash = "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"}, - {file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] python-dateutil = [ - {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, - {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] +pytz-deprecation-shim = [ + {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, + {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, +] regex = [ - {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, - {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, - {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, - {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, - {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, - {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, - {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, - {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, - {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, - {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, - {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, - {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, - {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, + {file = "regex-2022.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab69b4fe09e296261377d209068d52402fb85ef89dc78a9ac4a29a895f4e24a7"}, + {file = "regex-2022.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5bc5f921be39ccb65fdda741e04b2555917a4bced24b4df14eddc7569be3b493"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43eba5c46208deedec833663201752e865feddc840433285fbadee07b84b464d"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c68d2c04f7701a418ec2e5631b7f3552efc32f6bcc1739369c6eeb1af55f62e0"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:caa2734ada16a44ae57b229d45091f06e30a9a52ace76d7574546ab23008c635"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef806f684f17dbd6263d72a54ad4073af42b42effa3eb42b877e750c24c76f86"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be319f4eb400ee567b722e9ea63d5b2bb31464e3cf1b016502e3ee2de4f86f5c"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:42bb37e2b2d25d958c25903f6125a41aaaa1ed49ca62c103331f24b8a459142f"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fbc88d3ba402b5d041d204ec2449c4078898f89c4a6e6f0ed1c1a510ef1e221d"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:91e0f7e7be77250b808a5f46d90bf0032527d3c032b2131b63dee54753a4d729"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cb3652bbe6720786b9137862205986f3ae54a09dec8499a995ed58292bdf77c2"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:878c626cbca3b649e14e972c14539a01191d79e58934e3f3ef4a9e17f90277f8"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6df070a986fc064d865c381aecf0aaff914178fdf6874da2f2387e82d93cc5bd"}, + {file = "regex-2022.3.2-cp310-cp310-win32.whl", hash = "sha256:b549d851f91a4efb3e65498bd4249b1447ab6035a9972f7fc215eb1f59328834"}, + {file = "regex-2022.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:8babb2b5751105dc0aef2a2e539f4ba391e738c62038d8cb331c710f6b0f3da7"}, + {file = "regex-2022.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1977bb64264815d3ef016625adc9df90e6d0e27e76260280c63eca993e3f455f"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e73652057473ad3e6934944af090852a02590c349357b79182c1b681da2c772"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b22ff939a8856a44f4822da38ef4868bd3a9ade22bb6d9062b36957c850e404f"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:878f5d649ba1db9f52cc4ef491f7dba2d061cdc48dd444c54260eebc0b1729b9"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0008650041531d0eadecc96a73d37c2dc4821cf51b0766e374cb4f1ddc4e1c14"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06b1df01cf2aef3a9790858af524ae2588762c8a90e784ba00d003f045306204"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57484d39447f94967e83e56db1b1108c68918c44ab519b8ecfc34b790ca52bf7"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74d86e8924835f863c34e646392ef39039405f6ce52956d8af16497af4064a30"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:ae17fc8103f3b63345709d3e9654a274eee1c6072592aec32b026efd401931d0"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5f92a7cdc6a0ae2abd184e8dfd6ef2279989d24c85d2c85d0423206284103ede"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:5dcc4168536c8f68654f014a3db49b6b4a26b226f735708be2054314ed4964f4"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1e30762ddddb22f7f14c4f59c34d3addabc789216d813b0f3e2788d7bcf0cf29"}, + {file = "regex-2022.3.2-cp36-cp36m-win32.whl", hash = "sha256:286ff9ec2709d56ae7517040be0d6c502642517ce9937ab6d89b1e7d0904f863"}, + {file = "regex-2022.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d326ff80ed531bf2507cba93011c30fff2dd51454c85f55df0f59f2030b1687b"}, + {file = "regex-2022.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9d828c5987d543d052b53c579a01a52d96b86f937b1777bbfe11ef2728929357"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c87ac58b9baaf50b6c1b81a18d20eda7e2883aa9a4fb4f1ca70f2e443bfcdc57"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6c2441538e4fadd4291c8420853431a229fcbefc1bf521810fbc2629d8ae8c2"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f3356afbb301ec34a500b8ba8b47cba0b44ed4641c306e1dd981a08b416170b5"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d96eec8550fd2fd26f8e675f6d8b61b159482ad8ffa26991b894ed5ee19038b"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf668f26604e9f7aee9f8eaae4ca07a948168af90b96be97a4b7fa902a6d2ac1"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb0e2845e81bdea92b8281a3969632686502565abf4a0b9e4ab1471c863d8f3"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:87bc01226cd288f0bd9a4f9f07bf6827134dc97a96c22e2d28628e824c8de231"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:09b4b6ccc61d4119342b26246ddd5a04accdeebe36bdfe865ad87a0784efd77f"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:9557545c10d52c845f270b665b52a6a972884725aa5cf12777374e18f2ea8960"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:0be0c34a39e5d04a62fd5342f0886d0e57592a4f4993b3f9d257c1f688b19737"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7b103dffb9f6a47ed7ffdf352b78cfe058b1777617371226c1894e1be443afec"}, + {file = "regex-2022.3.2-cp37-cp37m-win32.whl", hash = "sha256:f8169ec628880bdbca67082a9196e2106060a4a5cbd486ac51881a4df805a36f"}, + {file = "regex-2022.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b9c16a807b17b17c4fa3a1d8c242467237be67ba92ad24ff51425329e7ae3d0"}, + {file = "regex-2022.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67250b36edfa714ba62dc62d3f238e86db1065fccb538278804790f578253640"}, + {file = "regex-2022.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5510932596a0f33399b7fff1bd61c59c977f2b8ee987b36539ba97eb3513584a"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f7ee2289176cb1d2c59a24f50900f8b9580259fa9f1a739432242e7d254f93"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d7a68fa53688e1f612c3246044157117403c7ce19ebab7d02daf45bd63913e"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf5317c961d93c1a200b9370fb1c6b6836cc7144fef3e5a951326912bf1f5a3"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad397bc7d51d69cb07ef89e44243f971a04ce1dca9bf24c992c362406c0c6573"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:297c42ede2c81f0cb6f34ea60b5cf6dc965d97fa6936c11fc3286019231f0d66"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af4d8cc28e4c7a2f6a9fed544228c567340f8258b6d7ea815b62a72817bbd178"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:452519bc4c973e961b1620c815ea6dd8944a12d68e71002be5a7aff0a8361571"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cb34c2d66355fb70ae47b5595aafd7218e59bb9c00ad8cc3abd1406ca5874f07"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d146e5591cb67c5e836229a04723a30af795ef9b70a0bbd913572e14b7b940f"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:03299b0bcaa7824eb7c0ebd7ef1e3663302d1b533653bfe9dc7e595d453e2ae9"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ccb0a4ab926016867260c24c192d9df9586e834f5db83dfa2c8fffb3a6e5056"}, + {file = "regex-2022.3.2-cp38-cp38-win32.whl", hash = "sha256:f7e8f1ee28e0a05831c92dc1c0c1c94af5289963b7cf09eca5b5e3ce4f8c91b0"}, + {file = "regex-2022.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:35ed2f3c918a00b109157428abfc4e8d1ffabc37c8f9abc5939ebd1e95dabc47"}, + {file = "regex-2022.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:55820bc631684172b9b56a991d217ec7c2e580d956591dc2144985113980f5a3"}, + {file = "regex-2022.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:83f03f0bd88c12e63ca2d024adeee75234d69808b341e88343b0232329e1f1a1"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42d6007722d46bd2c95cce700181570b56edc0dcbadbfe7855ec26c3f2d7e008"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:320c2f4106962ecea0f33d8d31b985d3c185757c49c1fb735501515f963715ed"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd3fe37353c62fd0eb19fb76f78aa693716262bcd5f9c14bb9e5aca4b3f0dc4"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e51ad1e6131c496b58d317bc9abec71f44eb1957d32629d06013a21bc99cac"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72bc3a5effa5974be6d965ed8301ac1e869bc18425c8a8fac179fbe7876e3aee"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e5602a9b5074dcacc113bba4d2f011d2748f50e3201c8139ac5b68cf2a76bd8b"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:729aa8ca624c42f309397c5fc9e21db90bf7e2fdd872461aabdbada33de9063c"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d6ecfd1970b3380a569d7b3ecc5dd70dba295897418ed9e31ec3c16a5ab099a5"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:13bbf0c9453c6d16e5867bda7f6c0c7cff1decf96c5498318bb87f8136d2abd4"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:58ba41e462653eaf68fc4a84ec4d350b26a98d030be1ab24aba1adcc78ffe447"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c0446b2871335d5a5e9fcf1462f954586b09a845832263db95059dcd01442015"}, + {file = "regex-2022.3.2-cp39-cp39-win32.whl", hash = "sha256:20e6a27959f162f979165e496add0d7d56d7038237092d1aba20b46de79158f1"}, + {file = "regex-2022.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9efa41d1527b366c88f265a227b20bcec65bda879962e3fc8a2aee11e81266d7"}, + {file = "regex-2022.3.2.tar.gz", hash = "sha256:79e5af1ff258bc0fe0bdd6f69bc4ae33935a898e3cbefbbccf22e88a27fa053b"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] "ruamel.yaml" = [ - {file = "ruamel.yaml-0.15.89-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c3c1af293b9e6a84b5da3954f4ae81b30d07a626717cfb6f099ffe0901ff1eac"}, - {file = "ruamel.yaml-0.15.89-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f004249eb8b873c4f48361dc814450e47dab53b5312334cff0a9d381a4589f03"}, - {file = "ruamel.yaml-0.15.89-cp27-cp27m-win32.whl", hash = "sha256:0ae64b150ec39667ce2815227271207d27a6218bc501b73e70a7403f2cf987ee"}, - {file = "ruamel.yaml-0.15.89-cp27-cp27m-win_amd64.whl", hash = "sha256:76aadb21b3186599057c8874104c8467270307e12faed124dc5ee1cfcd4e240f"}, - {file = "ruamel.yaml-0.15.89-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1bafe5773c559ef8e06d53f35bfcaad0510ef069db8b3fb50bf9816b3c531633"}, - {file = "ruamel.yaml-0.15.89-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:325b5b1df1b29ecaaa3dab6745ee0a35d51e58aa19b90cab8ba11c498d438cb7"}, - {file = "ruamel.yaml-0.15.89-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:fc6a2b2f4453944ab8bbe4d6d2eba0c864c098baa8487b08e4340a93a1f811c7"}, - {file = "ruamel.yaml-0.15.89-cp34-cp34m-win32.whl", hash = "sha256:551142c578813ee25e4952dda820701f0201d0292e8807e8fb5490d19bab8181"}, - {file = "ruamel.yaml-0.15.89-cp34-cp34m-win_amd64.whl", hash = "sha256:b76a4cf08e9392765cf6a25eae63a12fb27c6cc4a8ee5416a49cd54627151f46"}, - {file = "ruamel.yaml-0.15.89-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c64ae6da2da174fce281a088d7634c6e545db5fb989a3cecec2aa9cef2634a92"}, - {file = "ruamel.yaml-0.15.89-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:fffc4dd4097451c0ae1c6cb89bbff1a8248fda6366adb08d1c7bbc4ea2e6a637"}, - {file = "ruamel.yaml-0.15.89-cp35-cp35m-win32.whl", hash = "sha256:6d8bf658b64ebeccb67bccccd01833cdbe5369c1436365d41052d23e5cbf716f"}, - {file = "ruamel.yaml-0.15.89-cp35-cp35m-win_amd64.whl", hash = "sha256:bd9976f13d14f6cb6da1748c7678198b6f9346cbbe0ad18eb74c024a7a2c0d08"}, - {file = "ruamel.yaml-0.15.89-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ca742877ea6948cb80c70aaf5d76011694eb072d98264a704b04ffc2d4c0d440"}, - {file = "ruamel.yaml-0.15.89-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0e6aa488ec65fcaf7cca609d83a2b0e13d429a6080dd6ef3fd89dcbf67f13c1d"}, - {file = "ruamel.yaml-0.15.89-cp36-cp36m-win32.whl", hash = "sha256:aa4bfa597c58f2efcae98add337ada85ded45288936a15ddc2d29ff3f5ce3ddf"}, - {file = "ruamel.yaml-0.15.89-cp36-cp36m-win_amd64.whl", hash = "sha256:cfefc391d5a3a7e8b1c6195f364d2f11f898f07eef277ef8e93a4e3ccafbd7c2"}, - {file = "ruamel.yaml-0.15.89-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:181aeecf6d037e34c325c191b0a14188921964dd02c34a4d01bf7881060c22d0"}, - {file = "ruamel.yaml-0.15.89-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1085219e373381c8a4ac911be2f167d2c67485af82199db0abee11cfe06283d5"}, - {file = "ruamel.yaml-0.15.89-cp37-cp37m-win32.whl", hash = "sha256:20bc4549b86f7e5a558c06e51c97821d8b7658d4b01d3e21713a53db6f45779a"}, - {file = "ruamel.yaml-0.15.89-cp37-cp37m-win_amd64.whl", hash = "sha256:3e89d657a11fee61120d304efcab92409ea4a21c9629626cbb7aabbac7b713d9"}, - {file = "ruamel.yaml-0.15.89.tar.gz", hash = "sha256:86d034aa9e2ab3eacc5f75f5cd6a469a2af533b6d9e60ea92edbba540d21b9b7"}, + {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, + {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, +] +"ruamel.yaml.clib" = [ + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, + {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, ] scipy = [ {file = "scipy-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a15a1f3fc0abff33e792d6049161b7795909b40b97c6cc2934ed54384017ab76"}, @@ -650,12 +802,12 @@ signal-processing-algorithms = [ {file = "signal_processing_algorithms-1.3.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62c44bca7d411390624d44813f2cbf2c652dfe72aa646beaf060937fe8edcdad"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.4.2-py2.py3-none-any.whl", hash = "sha256:5e4f89971e8329a81f57e621e1ea273b8eda67797eb431355e384c296be82553"}, - {file = "slack_sdk-3.4.2.tar.gz", hash = "sha256:befc1365df93b84eac4592b9a2183411a748ecf662f5c51ce2fa7fdc3f5ee89f"}, + {file = "slack_sdk-3.15.2-py2.py3-none-any.whl", hash = "sha256:e1fa26786169176e707676decc287fd9d3d547bbc43c0a1a4f99eb373b07da94"}, + {file = "slack_sdk-3.15.2.tar.gz", hash = "sha256:128f3bb0b5b91454a3d5f140a61f3db370a0e1b50ffe0a8d9e9ebe0e894faed7"}, ] structlog = [ {file = "structlog-19.2.0-py2.py3-none-any.whl", hash = "sha256:6640e6690fc31d5949bc614c1a630464d3aaa625284aeb7c6e486c3010d73e12"}, @@ -669,50 +821,26 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] +tzdata = [ + {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, + {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, ] tzlocal = [ - {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, - {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, + {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, + {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, ] urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] validators = [ {file = "validators-0.18.2-py3-none-any.whl", hash = "sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd"}, diff --git a/pyproject.toml b/pyproject.toml index 92e5f082..7125d2a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,11 +11,11 @@ numpy = "1.19" python = "^3.8" python-dateutil = "^2.8.1" signal-processing-algorithms = "^1.3.2" -"ruamel.yaml" = "=0.15.89" +"ruamel.yaml" = "=0.17.21" requests = "^2.25.1" -pystache = "^0.5.4" +pystache = "^0.6.0" tabulate = "^0.8.7" -black = "^20.8b1" +black = "^22.3.0" validators = "^0.18.2" slack-sdk = "^3.4.2" diff --git a/tests/config_test.py b/tests/config_test.py index 79d093c0..dbf260f6 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -1,7 +1,7 @@ from pathlib import Path from hunter.config import load_config_from -from hunter.test_config import GraphiteTestConfig, CsvTestConfig +from hunter.test_config import GraphiteTestConfig, CsvTestConfig, HistoStatTestConfig def test_load_graphite_tests(): @@ -47,3 +47,13 @@ def test_load_test_groups(): groups = config.test_groups assert len(groups) == 2 assert len(groups["remote"]) == 2 + + +def test_load_histostat_config(): + config = load_config_from(Path("tests/resources/histostat_test_config.yaml")) + tests = config.tests + assert len(tests) == 1 + test = tests["histostat-sample"] + assert isinstance(test, HistoStatTestConfig) + # 14 tags * 12 tag_metrics == 168 unique metrics + assert len(test.fully_qualified_metric_names()) == 168 diff --git a/tests/importer_test.py b/tests/importer_test.py index a0e74bfb..676f4e8b 100644 --- a/tests/importer_test.py +++ b/tests/importer_test.py @@ -4,21 +4,33 @@ from hunter.csv_options import CsvOptions from hunter.graphite import DataSelector -from hunter.importer import CsvImporter -from hunter.test_config import CsvTestConfig, CsvMetric +from hunter.importer import CsvImporter, HistoStatImporter +from hunter.test_config import CsvTestConfig, CsvMetric, HistoStatTestConfig +SAMPLE_CSV = "tests/resources/sample.csv" -def test_import_csv(): - test = CsvTestConfig( + +def csv_test_config(file, csv_options=None): + return CsvTestConfig( name="test", - file="tests/resources/sample.csv", - csv_options=CsvOptions(), + file=file, + csv_options=csv_options if csv_options else CsvOptions(), time_column="time", metrics=[CsvMetric("m1", 1, 1.0, "metric1"), CsvMetric("m2", 1, 5.0, "metric2")], attributes=["commit"], ) + + +def data_selector(): + selector = DataSelector() + selector.since_time = datetime(1970, 1, 1, 1, 1, 1, tzinfo=pytz.UTC) + return selector + + +def test_import_csv(): + test = csv_test_config(SAMPLE_CSV) importer = CsvImporter() - series = importer.fetch_data(test_conf=test) + series = importer.fetch_data(test_conf=test, selector=data_selector()) assert len(series.data.keys()) == 2 assert len(series.time) == 10 assert len(series.data["m1"]) == 10 @@ -27,16 +39,9 @@ def test_import_csv(): def test_import_csv_with_metrics_filter(): - test = CsvTestConfig( - name="test", - file="tests/resources/sample.csv", - csv_options=CsvOptions(), - time_column="time", - metrics=[CsvMetric("m1", 1, 1.0, "metric1"), CsvMetric("m2", 1, 5.0, "metric2")], - attributes=["commit"], - ) + test = csv_test_config(SAMPLE_CSV) importer = CsvImporter() - selector = DataSelector() + selector = data_selector() selector.metrics = ["m2"] series = importer.fetch_data(test, selector=selector) assert len(series.data.keys()) == 1 @@ -46,17 +51,9 @@ def test_import_csv_with_metrics_filter(): def test_import_csv_with_time_filter(): - test = CsvTestConfig( - name="test", - file="tests/resources/sample.csv", - csv_options=CsvOptions(), - time_column="time", - metrics=[CsvMetric("m1", 1, 1.0, "metric1"), CsvMetric("m2", 1, 5.0, "metric2")], - attributes=["commit"], - ) - + test = csv_test_config(SAMPLE_CSV) importer = CsvImporter() - selector = DataSelector() + selector = data_selector() tz = pytz.timezone("Etc/GMT+1") selector.since_time = datetime(2021, 1, 5, 0, 0, 0, tzinfo=tz) selector.until_time = datetime(2021, 1, 7, 0, 0, 0, tzinfo=tz) @@ -68,17 +65,9 @@ def test_import_csv_with_time_filter(): def test_import_csv_with_unix_timestamps(): - test = CsvTestConfig( - name="test", - file="tests/resources/sample.csv", - csv_options=CsvOptions(), - time_column="time", - metrics=[CsvMetric("m1", 1, 1.0, "metric1"), CsvMetric("m2", 1, 5.0, "metric2")], - attributes=["commit"], - ) - + test = csv_test_config(SAMPLE_CSV) importer = CsvImporter() - series = importer.fetch_data(test_conf=test) + series = importer.fetch_data(test_conf=test, selector=data_selector()) assert len(series.data.keys()) == 2 assert len(series.time) == 10 assert len(series.data["m1"]) == 10 @@ -90,18 +79,9 @@ def test_import_csv_with_unix_timestamps(): def test_import_csv_semicolon_sep(): options = CsvOptions() options.delimiter = ";" - - test = CsvTestConfig( - name="test", - file="tests/resources/sample-semicolons.csv", - csv_options=options, - time_column="time", - metrics=[CsvMetric("m1", 1, 1.0, "metric1"), CsvMetric("m2", 1, 5.0, "metric2")], - attributes=["commit"], - ) - + test = csv_test_config("tests/resources/sample-semicolons.csv", options) importer = CsvImporter() - series = importer.fetch_data(test_conf=test) + series = importer.fetch_data(test_conf=test, selector=data_selector()) assert len(series.data.keys()) == 2 assert len(series.time) == 10 assert len(series.data["m1"]) == 10 @@ -110,18 +90,29 @@ def test_import_csv_semicolon_sep(): def test_import_csv_last_n_points(): - test = CsvTestConfig( - name="test", - file="tests/resources/sample.csv", - csv_options=CsvOptions(), - time_column="time", - metrics=[CsvMetric("m1", 1, 1.0, "metric1"), CsvMetric("m2", 1, 5.0, "metric2")], - attributes=["commit"], - ) + test = csv_test_config(SAMPLE_CSV) importer = CsvImporter() - selector = DataSelector() + selector = data_selector() selector.last_n_points = 5 series = importer.fetch_data(test, selector=selector) assert len(series.time) == 5 assert len(series.data["m2"]) == 5 assert len(series.attributes["commit"]) == 5 + + +def test_import_histostat(): + test = HistoStatTestConfig(name="test", file="tests/resources/histostat.csv") + importer = HistoStatImporter() + series = importer.fetch_data(test) + assert len(series.time) == 3 + assert len(series.data["initialize.result-success.count"]) == 3 + + +def test_import_histostat_last_n_points(): + test = HistoStatTestConfig(name="test", file="tests/resources/histostat.csv") + importer = HistoStatImporter() + selector = DataSelector() + selector.last_n_points = 2 + series = importer.fetch_data(test, selector=selector) + assert len(series.time) == 2 + assert len(series.data["initialize.result-success.count"]) == 2 diff --git a/tests/report_test.py b/tests/report_test.py index c63894ce..fe15a9c7 100644 --- a/tests/report_test.py +++ b/tests/report_test.py @@ -1,12 +1,17 @@ +import json + +import pytest + +from hunter.report import Report, ReportType from hunter.series import Series, Metric -from hunter.report import Report -def test_report(): +@pytest.fixture(scope="module") +def series(): series1 = [1.02, 0.95, 0.99, 1.00, 1.12, 0.90, 0.50, 0.51, 0.48, 0.48, 0.55] series2 = [2.02, 2.03, 2.01, 2.04, 1.82, 1.85, 1.79, 1.81, 1.80, 1.76, 1.78] time = list(range(len(series1))) - test = Series( + return Series( "test", branch=None, time=time, @@ -14,18 +19,44 @@ def test_report(): data={"series1": series1, "series2": series2}, attributes={}, ) - changepoints = test.analyze().change_points_by_time - report = Report(test, changepoints) - output = report.format_log_annotated() + + +@pytest.fixture(scope="module") +def change_points(series): + return series.analyze().change_points_by_time + + +@pytest.fixture(scope="module") +def report(series, change_points): + return Report(series, change_points) + + +def test_report(series, change_points): + report = Report(series, change_points) + output = report.produce_report("test", ReportType.LOG) assert "series1" in output assert "series2" in output assert "1.02" in output assert "0.55" in output assert "2.02" in output assert "1.78" in output - assert "%" in output + assert "-11.0%" in output + assert "-49.4%" in output # 2 lines for the header # 1 line per each time point # 3 lines per each change point - assert len(output.split("\n")) == len(time) + 2 + 3 * len(changepoints) + assert len(output.split("\n")) == len(series.time) + 2 + 3 * len(change_points) + + +def test_json_report(report): + output = report.produce_report("test_name_from_config", ReportType.JSON) + obj = json.loads(output) + expected = { + "test_name_from_config": [ + {"time": 4, "changes": [{"metric": "series2", "forward_change_percent": "-11"}]}, + {"time": 6, "changes": [{"metric": "series1", "forward_change_percent": "-49"}]}, + ] + } + assert isinstance(obj, dict) + assert obj == expected diff --git a/tests/resources/histostat.csv b/tests/resources/histostat.csv new file mode 100644 index 00000000..b7516714 --- /dev/null +++ b/tests/resources/histostat.csv @@ -0,0 +1,47 @@ +#logging stats for session scenario_20220407_172524_360 +#[Histogram log format version 1.0] +#[StartTime: 1649352325.178 (seconds since epoch), Thu Apr 07 17:25:25 UTC 2022] +#[TimeUnit: NANOSECONDS] +#Tag,Interval_Start,Interval_Length,count,min,p25,p50,p75,p90,p95,p98,p99,p999,p9999,max +Tag=initialize.tokenfiller,2.616,2.385,2028,1012224,1061887,1068031,1073151,1170431,1351679,2258943,4001791,10575871,15114239,15114239 +Tag=initialize.retry-delay,3.434,1.621,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.pages,3.435,1.621,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.skipped-tokens,3.437,1.620,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.resultset-size,3.438,1.620,6785,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.read_input,3.491,1.567,727,946,2217,2577,3001,4579,45055,49503,53055,161919,161919,161919 +Tag=initialize.strides.servicetime,3.495,1.567,8,1106247680,1135607807,1327497215,1394606079,1546649599,1546649599,1546649599,1546649599,1546649599,1546649599,1546649599 +Tag=initialize.bind,3.496,1.579,7606,26128,59391,74303,89599,113151,141183,213887,269567,10944511,12255231,12255231 +Tag=initialize.execute,3.497,1.585,7616,10528,32447,40927,57279,113535,135039,153343,174207,1992703,9560063,9560063 +Tag=initialize.result,3.497,1.588,6899,2488320,33488895,80805887,153878527,271843327,322699263,366215167,407371775,478937087,497025023,497025023 +Tag=initialize.result-success,3.498,1.594,6930,2607104,33685503,80936959,154402815,272105471,322437119,365953023,406323199,478937087,497025023,497025023 +Tag=initialize.tries,3.499,1.599,6975,1,1,1,1,1,1,1,1,1,1,1 +Tag=initialize.cycles.servicetime,3.499,1.600,6980,892862464,1310719999,1573912575,1677721599,1762656255,1828716543,1876951039,1901068287,1955594239,1990197247,1990197247 +Tag=initialize.phases.servicetime,3.5,1.606,7043,2709504,34242559,81526783,157024255,272105471,321912831,366215167,406323199,478674943,497025023,497025023 +Tag=initialize.tokenfiller,5.001,4.999,4614,1011200,1059839,1062911,1068031,1078271,1090559,1135615,1230847,6668287,12279807,12279807 +Tag=initialize.retry-delay,5.055,4.951,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.pages,5.056,4.952,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.skipped-tokens,5.057,4.952,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.resultset-size,5.058,4.952,47122,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.read_input,5.058,4.955,2340,137,782,885,1006,1215,1669,3281,4747,20287,26335,26335 +Tag=initialize.strides.servicetime,5.062,4.955,2342,510394368,1307574271,1578106879,1941962751,2264924159,2415919103,2606759935,2759852031,2986344447,3405774847,3405774847 +Tag=initialize.bind,5.075,4.956,46602,24064,45023,50879,57215,66367,74431,101567,145919,823807,6746111,8568831 +Tag=initialize.execute,5.082,4.953,46636,1840,8495,11903,15895,26735,34399,42207,47839,85567,1755135,6418431 +Tag=initialize.result,5.085,4.953,47342,1600512,7856127,20414463,51675135,148766719,192544767,239992831,269746175,347602943,439615487,552599551 +Tag=initialize.result-success,5.092,4.954,47403,1610752,7819263,20332543,51380223,148373503,192282623,239861759,269221887,347602943,439877631,551550975 +Tag=initialize.tries,5.098,4.955,47431,1,1,1,1,1,1,1,1,1,1,1 +Tag=initialize.cycles.servicetime,5.099,4.955,47428,1711276032,1988100095,2006974463,2027945983,2092957695,2146435071,2187329535,2212495359,2258632703,2296381439,2321547263 +Tag=initialize.phases.servicetime,5.106,4.955,47421,1669120,7819263,20234239,50954239,147324927,191365119,239206399,268042239,345767935,424673279,553648127 +Tag=initialize.tokenfiller,10.0,5.001,4452,1013760,1052671,1061887,1067007,1076223,1083391,1101823,1464319,15171583,28835839,28835839 +Tag=initialize.retry-delay,10.006,5.000,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.pages,10.008,4.999,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.skipped-tokens,10.009,4.998,0,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.resultset-size,10.01,4.998,48887,0,0,0,0,0,0,0,0,0,0,0 +Tag=initialize.read_input,10.013,4.995,2439,88,764,865,962,1156,1355,3035,6347,20031,28895,28895 +Tag=initialize.strides.servicetime,10.017,4.992,2436,508035072,1197473791,1421869055,1659895807,1925185535,2095054847,2296381439,2397044735,2717908991,2852126719,2852126719 +Tag=initialize.bind,10.031,4.985,48696,25392,45919,51679,57727,65663,71487,79999,87615,753663,6455295,6529023 +Tag=initialize.execute,10.035,4.983,48685,1704,8831,11575,14055,17807,21567,26527,30575,57983,205439,3825663 +Tag=initialize.result,10.038,4.983,48693,1349632,2418687,3174399,6021119,16859135,20299775,24412159,33472511,61046783,74907647,83296255 +Tag=initialize.result-success,10.046,4.979,48646,1361920,2428927,3180543,6033407,16875519,20299775,24444927,33488895,61046783,74973183,83296255 +Tag=initialize.tries,10.053,4.977,48621,1,1,1,1,1,1,1,1,1,1,1 +Tag=initialize.cycles.servicetime,10.054,4.976,48620,1982857216,2001731583,2013265919,2028994559,2076180479,2107637759,2112880639,2114977791,2116026367,2117074943,2118123519 +Tag=initialize.phases.servicetime,10.061,4.976,48636,1408000,2488319,3239935,6103039,16924671,20365311,24526847,33521663,61079551,75038719,83296255 diff --git a/tests/resources/histostat_test_config.yaml b/tests/resources/histostat_test_config.yaml new file mode 100644 index 00000000..2112ce72 --- /dev/null +++ b/tests/resources/histostat_test_config.yaml @@ -0,0 +1,4 @@ +tests: + histostat-sample: + type: histostat + file: tests/resources/histostat.csv diff --git a/tests/slack_notification_test.py b/tests/slack_notification_test.py index 175d3ab8..a224c2d6 100644 --- a/tests/slack_notification_test.py +++ b/tests/slack_notification_test.py @@ -1,21 +1,20 @@ import json - from datetime import datetime -from dateutil import tz from typing import Dict, List +from dateutil import tz + from hunter.data_selector import DataSelector from hunter.series import Series, Metric from hunter.slack import SlackNotifier, NotificationError - NOTIFICATION_CHANNELS = ["a-channel", "b-channel"] class DispatchTrackingMockClient: dispatches: Dict[str, List[List[object]]] = dict() - def chat_postMessage(self, channel: str=None, blocks: List[object]=None): + def chat_postMessage(self, channel: str = None, blocks: List[object] = None): if not channel or not blocks: raise NotificationError(f"Invalid dispatch: {channel} {blocks}") if channel not in self.dispatches: @@ -76,10 +75,12 @@ def test_blocks_dispatch(): analyzed_series = test.analyze() mock_client = DispatchTrackingMockClient() notifier = SlackNotifier(client=mock_client) - notifier.notify(test_analyzed_series={"test": analyzed_series}, - selector=data_selector, - channels=NOTIFICATION_CHANNELS, - since=since_time) + notifier.notify( + test_analyzed_series={"test": analyzed_series}, + selector=data_selector, + channels=NOTIFICATION_CHANNELS, + since=since_time, + ) dispatches = mock_client.dispatches assert list(dispatches.keys()) == NOTIFICATION_CHANNELS, "Wrong channels were notified" for channel in NOTIFICATION_CHANNELS: diff --git a/tests/util_test.py b/tests/util_test.py index 2e529537..63586bad 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -1,4 +1,12 @@ -from hunter.util import * +from hunter.util import ( + merge_sorted, + remove_common_prefix, + insert_multiple, + sliding_window, + merge_dicts, + merge_dict_list, + interpolate, +) def test_merge_sorted():