Repository: kurusugawa-computer/annofab-cli (language: python)

Issue:
`statistics list_annotation_count`
# 現状
attributes.countのCSVは以下のようになっている。

```
  ,,,,Cat,Cat,...
  ,,,,occluded,occluded,...
  task_id,task_status,task_phase,task_phase_stage,input_data_count,True,False,...
  task1,break,acceptance,1,5,2,0,...
  task2,complete,acceptance,1,5,2,0,...
```

task_idやtask_status列は3行目に記載されている。

# やりたいこと
task_idやtask_status列を1行目に記載する。そうするとpandasなどでCSVを扱いやすいかもしれない。

```
task_id,task_status,task_phase,task_phase_stage,input_data_count,Cat,Cat,...
,,,,occluded,occluded,...
,,,,True,False,...
task1,break,acceptance,1,5,2,0,...
task2,complete,acceptance,1,5,2,0,...
```



Discussion / hints:

Fix patch:
diff --git a/annofabcli/__version__.py b/annofabcli/__version__.py
index 35186d1a..a029f985 100644
--- a/annofabcli/__version__.py
+++ b/annofabcli/__version__.py
@@ -1,1 +1,1 @@
-__version__ = "1.57.2"
+__version__ = "1.57.3"
diff --git a/annofabcli/common/utils.py b/annofabcli/common/utils.py
index 6c165d3c..3fe28184 100644
--- a/annofabcli/common/utils.py
+++ b/annofabcli/common/utils.py
@@ -6,7 +6,7 @@
 import re
 import sys
 from pathlib import Path
-from typing import Any, Callable, Dict, List, Optional, Set, TypeVar
+from typing import Any, Callable, Dict, List, Optional, Set, TypeVar, Union
 
 import dateutil.parser
 import isodate
@@ -47,7 +47,7 @@ def duplicated_set(target_list: List[T]) -> Set[T]:
     return {x for x in set(target_list) if target_list.count(x) > 1}
 
 
-def output_string(target: str, output: Optional[str] = None) -> None:
+def output_string(target: str, output: Optional[Union[str, Path]] = None) -> None:
     """
     文字列を出力する。
 
@@ -58,13 +58,14 @@ def output_string(target: str, output: Optional[str] = None) -> None:
     if output is None:
         print(target)
     else:
-        Path(output).parent.mkdir(parents=True, exist_ok=True)
-        with open(output, mode="w", encoding="utf_8") as f:
+        p_output = output if isinstance(output, Path) else Path(output)
+        p_output.parent.mkdir(parents=True, exist_ok=True)
+        with p_output.open(mode="w", encoding="utf_8") as f:
             f.write(target)
             logger.info(f"{output} を出力しました。")
 
 
-def print_json(target: Any, is_pretty: bool = False, output: Optional[str] = None) -> None:
+def print_json(target: Any, is_pretty: bool = False, output: Optional[Union[str, Path]] = None) -> None:
     """
     JSONを出力する。
 
@@ -80,11 +81,13 @@ def print_json(target: Any, is_pretty: bool = False, output: Optional[str] = Non
         output_string(json.dumps(target, ensure_ascii=False), output)
 
 
-def print_csv(df: pandas.DataFrame, output: Optional[str] = None, to_csv_kwargs: Optional[Dict[str, Any]] = None):
+def print_csv(
+    df: pandas.DataFrame, output: Optional[Union[str, Path]] = None, to_csv_kwargs: Optional[Dict[str, Any]] = None
+):
     if output is not None:
         Path(output).parent.mkdir(parents=True, exist_ok=True)
 
-    path_or_buf = sys.stdout if output is None else output
+    path_or_buf = sys.stdout if output is None else str(output)
 
     kwargs = copy.deepcopy(DEFAULT_CSV_FORMAT)
     if to_csv_kwargs is None:
@@ -97,13 +100,16 @@ def print_csv(df: pandas.DataFrame, output: Optional[str] = None, to_csv_kwargs:
         logger.info(f"{output} を出力しました。")
 
 
-def print_id_list(id_list: List[Any], output: Optional[str]):
+def print_id_list(id_list: List[Any], output: Optional[Union[str, Path]]):
     s = "\n".join(id_list)
     output_string(s, output)
 
 
 def print_according_to_format(
-    target: Any, arg_format: FormatArgument, output: Optional[str] = None, csv_format: Optional[Dict[str, Any]] = None
+    target: Any,
+    arg_format: FormatArgument,
+    output: Optional[Union[str, Path]] = None,
+    csv_format: Optional[Dict[str, Any]] = None,
 ):
     """
     コマンドライン引数 ``--format`` の値にしたがって、内容を出力する。
diff --git a/annofabcli/statistics/list_annotation_count.py b/annofabcli/statistics/list_annotation_count.py
index ae3b7b45..e37ca308 100644
--- a/annofabcli/statistics/list_annotation_count.py
+++ b/annofabcli/statistics/list_annotation_count.py
@@ -6,8 +6,10 @@
 import logging
 import tempfile
 import zipfile
-from dataclasses import dataclass
+from collections import defaultdict
+from dataclasses import dataclass, field
 from enum import Enum
+from functools import partial
 from pathlib import Path
 from typing import Any, Collection, Counter, Iterator, Optional, Tuple
 
@@ -22,19 +24,20 @@
     lazy_parse_simple_annotation_zip,
     lazy_parse_simple_annotation_zip_by_task,
 )
-from dataclasses_json import DataClassJsonMixin
+from dataclasses_json import DataClassJsonMixin, config
 
 import annofabcli
 import annofabcli.common.cli
 from annofabcli import AnnofabApiFacade
 from annofabcli.common.cli import AbstractCommandLineInterface, ArgumentParser, build_annofabapi_resource_and_login
 from annofabcli.common.download import DownloadingFile
+from annofabcli.common.enums import FormatArgument
 from annofabcli.common.facade import (
     TaskQuery,
     convert_annotation_specs_labels_v2_to_v1,
     match_annotation_with_task_query,
 )
-from annofabcli.common.utils import print_csv
+from annofabcli.common.utils import print_csv, print_json
 from annofabcli.common.visualize import AddProps, MessageLocale
 
 logger = logging.getLogger(__name__)
@@ -50,33 +53,64 @@
 AttributeKeys = Collection[Collection]
 
 
+class CsvType(Enum):
+    """出力するCSVの種類"""
+
+    LABEL = "label"
+    """ラベルごとのアノテーション数を出力"""
+    ATTRIBUTE = "attribute"
+    """属性値ごとのアノテーション数を出力"""
+
+
 class GroupBy(Enum):
     TASK_ID = "task_id"
     INPUT_DATA_ID = "input_data_id"
 
 
+def encode_annotation_count_by_attribute(
+    annotation_count_by_attribute: Counter[AttributesKey],
+) -> dict[str, dict[str, dict[str, int]]]:
+    """annotation_count_by_attributeを `{label_name: {attribute_name: {attribute_value: annotation_count}}}`のdictに変換します。
+    JSONへの変換用関数です。
+    """
+
+    def _factory():
+        """入れ子の辞書を利用できるようにするための関数"""
+        return collections.defaultdict(_factory)
+
+    result: dict[str, dict[str, dict[str, int]]] = defaultdict(_factory)
+    for (label_name, attribute_name, attribute_value), annotation_count in annotation_count_by_attribute.items():
+        result[label_name][attribute_name][attribute_value] = annotation_count
+    return result
+
+
 @dataclass(frozen=True)
 class AnnotationCounter(abc.ABC):
-    labels_counter: Counter[str]
-    attributes_counter: Counter[AttributesKey]
+    annotation_count: int
+    annotation_count_by_label: Counter[str]
+    annotation_count_by_attribute: Counter[AttributesKey] = field(
+        metadata=config(
+            encoder=encode_annotation_count_by_attribute,
+        )
+    )
 
 
 @dataclass(frozen=True)
 class AnnotationCounterByTask(AnnotationCounter, DataClassJsonMixin):
 
     task_id: str
-    task_status: TaskStatus
-    task_phase: TaskPhase
-    task_phase_stage: int
+    status: TaskStatus
+    phase: TaskPhase
+    phase_stage: int
     input_data_count: int
 
 
 @dataclass(frozen=True)
 class AnnotationCounterByInputData(AnnotationCounter, DataClassJsonMixin):
     task_id: str
-    task_status: TaskStatus
-    task_phase: TaskPhase
-    task_phase_stage: int
+    status: TaskStatus
+    phase: TaskPhase
+    phase_stage: int
 
     input_data_id: str
     input_data_name: str
@@ -127,33 +161,47 @@ def get_annotation_counter(
 
 
         """
+
+        def convert_attribute_value_to_key(value: Any) -> str:
+            if isinstance(value, bool):
+                # bool値をCSVの列名やJSONのキーとして扱う場合、`True/False`だとPythonに依存したように見えてしまうので（本当？）、`true/false`に変換する
+                if value:
+                    return "true"
+                elif not value:
+                    return "false"
+            return str(value)
+
         details = simple_annotation["details"]
 
-        labels_counter = collections.Counter([e["label"] for e in details])
+        annotation_count_by_label = collections.Counter([e["label"] for e in details])
         if target_labels is not None:
-            labels_counter = collections.Counter({k: v for k, v in labels_counter.items() if k in target_labels})
+            annotation_count_by_label = collections.Counter(
+                {k: v for k, v in annotation_count_by_label.items() if k in target_labels}
+            )
 
         attributes_list = []
         for detail in details:
             label = detail["label"]
             for attribute, value in detail["attributes"].items():
-                attributes_list.append((label, attribute, str(value)))
+                # 属性値を json.dumps関数で変換している理由： bool値の表現をJSONに合わせるため
+                attributes_list.append((label, attribute, convert_attribute_value_to_key(value)))
 
-        attributes_counter = collections.Counter(attributes_list)
+        annotation_count_by_attribute = collections.Counter(attributes_list)
         if target_attributes is not None:
-            attributes_counter = collections.Counter(
-                {key: count for key, count in attributes_counter.items() if key in target_attributes}
+            annotation_count_by_attribute = collections.Counter(
+                {key: count for key, count in annotation_count_by_attribute.items() if key in target_attributes}
             )
 
         return AnnotationCounterByInputData(
             task_id=simple_annotation["task_id"],
-            task_phase=TaskPhase(simple_annotation["task_phase"]),
-            task_phase_stage=simple_annotation["task_phase_stage"],
-            task_status=TaskStatus(simple_annotation["task_status"]),
+            phase=TaskPhase(simple_annotation["task_phase"]),
+            phase_stage=simple_annotation["task_phase_stage"],
+            status=TaskStatus(simple_annotation["task_status"]),
             input_data_id=simple_annotation["input_data_id"],
             input_data_name=simple_annotation["input_data_name"],
-            labels_counter=labels_counter,
-            attributes_counter=attributes_counter,
+            annotation_count=sum(annotation_count_by_label.values()),
+            annotation_count_by_label=annotation_count_by_label,
+            annotation_count_by_attribute=annotation_count_by_attribute,
         )
 
     @classmethod
@@ -206,7 +254,7 @@ def get_annotation_counter_list(
         return counter_list
 
     @classmethod
-    def print_labels_count(
+    def print_labels_count_csv(
         cls,
         counter_list: list[AnnotationCounterByInputData],
         output_file: Path,
@@ -218,18 +266,27 @@ def to_dict(c: AnnotationCounterByInputData) -> dict[str, Any]:
                 "input_data_id": c.input_data_id,
                 "input_data_name": c.input_data_name,
                 "task_id": c.task_id,
-                "task_status": c.task_status.value,
-                "task_phase": c.task_phase.value,
-                "task_phase_stage": c.task_phase_stage,
+                "status": c.status.value,
+                "phase": c.phase.value,
+                "phase_stage": c.phase_stage,
+                "annotation_count": c.annotation_count,
             }
-            d.update(c.labels_counter)
+            d.update(c.annotation_count_by_label)
             return d
 
-        basic_columns = ["input_data_id", "input_data_name", "task_id", "task_status", "task_phase", "task_phase_stage"]
+        basic_columns = [
+            "task_id",
+            "status",
+            "phase",
+            "phase_stage",
+            "input_data_id",
+            "input_data_name",
+            "annotation_count",
+        ]
         if label_columns is not None:
             value_columns = label_columns
         else:
-            label_column_set = {label for c in counter_list for label in c.labels_counter}
+            label_column_set = {label for c in counter_list for label in c.annotation_count_by_label}
             value_columns = sorted(list(label_column_set))
 
         columns = basic_columns + value_columns
@@ -241,7 +298,7 @@ def to_dict(c: AnnotationCounterByInputData) -> dict[str, Any]:
         print_csv(df, output=str(output_file), to_csv_kwargs=csv_format)
 
     @classmethod
-    def print_attributes_count(
+    def print_attributes_count_csv(
         cls,
         counter_list: list[AnnotationCounterByInputData],
         output_file: Path,
@@ -250,30 +307,32 @@ def print_attributes_count(
     ):
         def to_cell(c: AnnotationCounterByInputData) -> dict[tuple[str, str, str], Any]:
             cell = {
-                ("", "", "input_data_id"): c.input_data_id,
-                ("", "", "input_data_name"): c.input_data_name,
-                ("", "", "task_id"): c.task_id,
-                ("", "", "task_status"): c.task_status.value,
-                ("", "", "task_phase"): c.task_phase.value,
-                ("", "", "task_phase_stage"): c.task_phase_stage,
+                ("input_data_id", "", ""): c.input_data_id,
+                ("input_data_name", "", ""): c.input_data_name,
+                ("task_id", "", ""): c.task_id,
+                ("status", "", ""): c.status.value,
+                ("phase", "", ""): c.phase.value,
+                ("phase_stage", "", ""): c.phase_stage,
+                ("annotation_count", "", ""): c.annotation_count,
             }
-            cell.update(c.attributes_counter)
+            cell.update(c.annotation_count_by_attribute)
 
             return cell
 
         basic_columns = [
-            ("", "", "input_data_id"),
-            ("", "", "input_data_name"),
-            ("", "", "task_id"),
-            ("", "", "task_status"),
-            ("", "", "task_phase"),
-            ("", "", "task_phase_stage"),
+            ("task_id", "", ""),
+            ("status", "", ""),
+            ("phase", "", ""),
+            ("phase_stage", "", ""),
+            ("input_data_id", "", ""),
+            ("input_data_name", "", ""),
+            ("annotation_count", "", ""),
         ]
 
         if attribute_columns is not None:
             value_columns = attribute_columns
         else:
-            attr_key_set = {attr_key for c in counter_list for attr_key in c.attributes_counter}
+            attr_key_set = {attr_key for c in counter_list for attr_key in c.annotation_count_by_attribute}
             value_columns = sorted(list(attr_key_set))
 
         columns = basic_columns + value_columns
@@ -305,8 +364,8 @@ def get_annotation_counter(
 
         """
 
-        labels_counter: Counter[str] = collections.Counter()
-        attributes_counter: Counter[Tuple[str, str, str]] = collections.Counter()
+        annotation_count_by_label: Counter[str] = collections.Counter()
+        annotation_count_by_attribute: Counter[Tuple[str, str, str]] = collections.Counter()
 
         last_simple_annotation = None
         input_data_count = 0
@@ -316,22 +375,23 @@ def get_annotation_counter(
             input_data = ListAnnotationCounterByInputData.get_annotation_counter(
                 simple_annotation_dict, target_labels=target_labels, target_attributes=target_attributes
             )
-            labels_counter += input_data.labels_counter
-            attributes_counter += input_data.attributes_counter
+            annotation_count_by_label += input_data.annotation_count_by_label
+            annotation_count_by_attribute += input_data.annotation_count_by_attribute
             last_simple_annotation = simple_annotation_dict
             input_data_count += 1
 
         if last_simple_annotation is None:
-            raise RuntimeError(f"{task_parser.task_id} ディレクトリにはjsonファイルが１つも含まれていません。")
+            raise RuntimeError(f"{task_parser.task_id} ディレクトリにはjsonファイルが1つも含まれていません。")
 
         return AnnotationCounterByTask(
             task_id=last_simple_annotation["task_id"],
-            task_status=TaskStatus(last_simple_annotation["task_status"]),
-            task_phase=TaskPhase(last_simple_annotation["task_phase"]),
-            task_phase_stage=last_simple_annotation["task_phase_stage"],
+            status=TaskStatus(last_simple_annotation["task_status"]),
+            phase=TaskPhase(last_simple_annotation["task_phase"]),
+            phase_stage=last_simple_annotation["task_phase_stage"],
             input_data_count=input_data_count,
-            labels_counter=labels_counter,
-            attributes_counter=attributes_counter,
+            annotation_count=sum(annotation_count_by_label.values()),
+            annotation_count_by_label=annotation_count_by_label,
+            annotation_count_by_attribute=annotation_count_by_attribute,
         )
 
     @classmethod
@@ -389,7 +449,7 @@ def get_annotation_counter_list(
         return counter_list
 
     @classmethod
-    def print_labels_count(
+    def print_labels_count_csv(
         cls,
         counter_list: list[AnnotationCounterByTask],
         output_file: Path,
@@ -399,19 +459,28 @@ def print_labels_count(
         def to_dict(c: AnnotationCounterByTask) -> dict[str, Any]:
             d = {
                 "task_id": c.task_id,
-                "task_status": c.task_status.value,
-                "task_phase": c.task_phase.value,
-                "task_phase_stage": c.task_phase_stage,
+                "status": c.status.value,
+                "phase": c.phase.value,
+                "phase_stage": c.phase_stage,
                 "input_data_count": c.input_data_count,
+                "annotation_count": c.annotation_count,
             }
-            d.update(c.labels_counter)
+            # キーをラベル名、値をラベルごとのアノテーション数にしたdictに変換する
+            d.update(c.annotation_count_by_label)
             return d
 
-        basic_columns = ["task_id", "task_status", "task_phase", "task_phase_stage", "input_data_count"]
+        basic_columns = [
+            "task_id",
+            "status",
+            "phase",
+            "phase_stage",
+            "input_data_count",
+            "annotation_count",
+        ]
         if label_columns is not None:
             value_columns = label_columns
         else:
-            label_column_set = {label for c in counter_list for label in c.labels_counter}
+            label_column_set = {label for c in counter_list for label in c.annotation_count_by_label}
             value_columns = sorted(list(label_column_set))
 
         columns = basic_columns + value_columns
@@ -423,7 +492,7 @@ def to_dict(c: AnnotationCounterByTask) -> dict[str, Any]:
         print_csv(df, output=str(output_file), to_csv_kwargs=csv_format)
 
     @classmethod
-    def print_attributes_count(
+    def print_attributes_count_csv(
         cls,
         counter_list: list[AnnotationCounterByTask],
         output_file: Path,
@@ -432,31 +501,29 @@ def print_attributes_count(
     ):
         def to_cell(c: AnnotationCounterByTask) -> dict[AttributesKey, Any]:
             cell = {
-                ("", "", "task_id"): c.task_id,
-                ("", "", "task_status"): c.task_status.value,
-                ("", "", "task_phase"): c.task_phase.value,
-                ("", "", "task_phase_stage"): c.task_phase_stage,
-                ("", "", "input_data_count"): c.input_data_count,
+                ("task_id", "", ""): c.task_id,
+                ("status", "", ""): c.status.value,
+                ("phase", "", ""): c.phase.value,
+                ("phase_stage", "", ""): c.phase_stage,
+                ("input_data_count", "", ""): c.input_data_count,
+                ("annotation_count", "", ""): c.annotation_count,
             }
-            cell.update(c.attributes_counter)
+            cell.update(c.annotation_count_by_attribute)
             return cell
 
-        # if len(attribute_columns) == 0:
-        #     logger.warning(f"アノテーション仕様に集計対象の属性が定義されていないため、'{output_file}' は出力しません。")
-        #     return
-
         basic_columns = [
-            ("", "", "task_id"),
-            ("", "", "task_status"),
-            ("", "", "task_phase"),
-            ("", "", "task_phase_stage"),
-            ("", "", "input_data_count"),
+            ("task_id", "", ""),
+            ("status", "", ""),
+            ("phase", "", ""),
+            ("phase_stage", "", ""),
+            ("input_data_count", "", ""),
+            ("annotation_count", "", ""),
         ]
 
         if attribute_columns is not None:
             value_columns = attribute_columns
         else:
-            attr_key_set = {attr_key for c in counter_list for attr_key in c.attributes_counter}
+            attr_key_set = {attr_key for c in counter_list for attr_key in c.annotation_count_by_attribute}
             value_columns = sorted(list(attr_key_set))
 
         columns = basic_columns + value_columns
@@ -497,8 +564,8 @@ def _get_target_attributes_columns(annotation_specs_labels: list[dict[str, Any]]
                         target_attributes_columns.append((label_name_en, attribute_name_en, choice_name_en))
 
                 elif AdditionalDataDefinitionType(attribute["type"]) == AdditionalDataDefinitionType.FLAG:
-                    target_attributes_columns.append((label_name_en, attribute_name_en, "True"))
-                    target_attributes_columns.append((label_name_en, attribute_name_en, "False"))
+                    target_attributes_columns.append((label_name_en, attribute_name_en, "true"))
+                    target_attributes_columns.append((label_name_en, attribute_name_en, "false"))
 
                 else:
                     continue
@@ -548,19 +615,14 @@ def print_annotation_counter_csv(
         project_id: str,
         annotation_path: Path,
         group_by: GroupBy,
-        output_dir: Path,
+        csv_type: CsvType,
+        output_file: Path,
         *,
         target_task_ids: Optional[Collection[str]] = None,
         task_query: Optional[TaskQuery] = None,
     ):
-
-        labels_count_csv = output_dir / "labels_count.csv"
-        attributes_count_csv = output_dir / "attributes_count.csv"
-
         # 集計対象の属性を、選択肢系の属性にする
         label_columns, attribute_columns = self.get_target_columns(project_id)
-        if len(attribute_columns) == 0:
-            logger.info(f"アノテーション仕様に集計対象の属性が定義されていないため、{attributes_count_csv} は出力しません。")
 
         if group_by == GroupBy.INPUT_DATA_ID:
             counter_list_by_input_data = ListAnnotationCounterByInputData.get_annotation_counter_list(
@@ -570,13 +632,16 @@ def print_annotation_counter_csv(
                 target_attributes=attribute_columns,
             )
 
-            ListAnnotationCounterByInputData.print_labels_count(
-                counter_list_by_input_data, labels_count_csv, label_columns=label_columns
-            )
-
-            if len(attribute_columns) > 0:
-                ListAnnotationCounterByInputData.print_attributes_count(
-                    counter_list_by_input_data, attributes_count_csv, attribute_columns=attribute_columns
+            if csv_type == CsvType.LABEL:
+                ListAnnotationCounterByInputData.print_labels_count_csv(
+                    counter_list_by_input_data, output_file, label_columns=label_columns
+                )
+            elif csv_type == CsvType.ATTRIBUTE:
+                if len(attribute_columns) == 0:
+                    logger.error(f"アノテーション仕様に集計対象の属性が定義されていないため、{output_file} は出力しません。")
+                    return
+                ListAnnotationCounterByInputData.print_attributes_count_csv(
+                    counter_list_by_input_data, output_file, attribute_columns=attribute_columns
                 )
 
         elif group_by == GroupBy.TASK_ID:
@@ -586,19 +651,103 @@ def print_annotation_counter_csv(
                 task_query=task_query,
                 target_attributes=attribute_columns,
             )
+            if csv_type == CsvType.LABEL:
+                ListAnnotationCounterByTask.print_labels_count_csv(
+                    counter_list_by_task, output_file, label_columns=label_columns
+                )
+            elif csv_type == CsvType.ATTRIBUTE:
+                if len(attribute_columns) == 0:
+                    logger.error(f"アノテーション仕様に集計対象の属性が定義されていないため、{output_file} は出力しません。")
+                    return
+                ListAnnotationCounterByTask.print_attributes_count_csv(
+                    counter_list_by_task, output_file, attribute_columns=attribute_columns
+                )
+
+        else:
+            raise RuntimeError(f"group_by='{group_by}'が対象外です。")
+
+    def print_annotation_counter_json(
+        self,
+        project_id: str,
+        annotation_path: Path,
+        group_by: GroupBy,
+        output_file: Path,
+        *,
+        target_task_ids: Optional[Collection[str]] = None,
+        task_query: Optional[TaskQuery] = None,
+        json_is_pretty: bool = False,
+    ):
+        """ラベルごと/属性ごとのアノテーション数をJSONファイルに出力します。"""
+        # 集計対象の属性を、選択肢系の属性にする
+        _, attribute_columns = self.get_target_columns(project_id)
 
-            ListAnnotationCounterByTask.print_labels_count(
-                counter_list_by_task, labels_count_csv, label_columns=label_columns
+        if group_by == GroupBy.INPUT_DATA_ID:
+            counter_list_by_input_data = ListAnnotationCounterByInputData.get_annotation_counter_list(
+                annotation_path,
+                target_task_ids=target_task_ids,
+                task_query=task_query,
+                target_attributes=attribute_columns,
             )
 
-            if len(attribute_columns) > 0:
-                ListAnnotationCounterByTask.print_attributes_count(
-                    counter_list_by_task, attributes_count_csv, attribute_columns=attribute_columns
-                )
+            print_json(
+                [e.to_dict(encode_json=True) for e in counter_list_by_input_data],
+                is_pretty=json_is_pretty,
+                output=output_file,
+            )
+
+        elif group_by == GroupBy.TASK_ID:
+            counter_list_by_task = ListAnnotationCounterByTask.get_annotation_counter_list(
+                annotation_path,
+                target_task_ids=target_task_ids,
+                task_query=task_query,
+                target_attributes=attribute_columns,
+            )
+            print_json(
+                [e.to_dict(encode_json=True) for e in counter_list_by_task],
+                is_pretty=json_is_pretty,
+                output=output_file,
+            )
 
         else:
             raise RuntimeError(f"group_by='{group_by}'が対象外です。")
 
+    def print_annotation_counter(
+        self,
+        project_id: str,
+        annotation_path: Path,
+        group_by: GroupBy,
+        output_file: Path,
+        arg_format: FormatArgument,
+        *,
+        target_task_ids: Optional[Collection[str]] = None,
+        task_query: Optional[TaskQuery] = None,
+        csv_type: Optional[CsvType] = None,
+    ):
+        """ラベルごと/属性ごとのアノテーション数を出力します。"""
+        if arg_format == FormatArgument.CSV:
+            assert csv_type is not None
+            self.print_annotation_counter_csv(
+                project_id=project_id,
+                annotation_path=annotation_path,
+                group_by=group_by,
+                csv_type=csv_type,
+                output_file=output_file,
+                target_task_ids=target_task_ids,
+                task_query=task_query,
+            )
+
+        elif arg_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
+            json_is_pretty = arg_format == FormatArgument.PRETTY_JSON
+            self.print_annotation_counter_json(
+                project_id=project_id,
+                annotation_path=annotation_path,
+                group_by=group_by,
+                output_file=output_file,
+                target_task_ids=target_task_ids,
+                task_query=task_query,
+                json_is_pretty=json_is_pretty,
+            )
+
 
 class ListAnnotationCount(AbstractCommandLineInterface):
     """
@@ -609,7 +758,6 @@ def main(self):
         args = self.args
 
         project_id = args.project_id
-        output_dir: Path = args.output_dir
         super().validate_project(project_id, project_member_roles=None)
 
         annotation_path = Path(args.annotation) if args.annotation is not None else None
@@ -622,9 +770,22 @@ def main(self):
         )
 
         group_by = GroupBy(args.group_by)
-
+        csv_type = CsvType(args.type)
+        output_file: Path = args.output
+        arg_format = FormatArgument(args.format)
         main_obj = ListAnnotationCountMain(self.service)
 
+        func = partial(
+            main_obj.print_annotation_counter,
+            project_id=project_id,
+            group_by=group_by,
+            csv_type=csv_type,
+            arg_format=arg_format,
+            output_file=output_file,
+            target_task_ids=task_id_list,
+            task_query=task_query,
+        )
+
         if annotation_path is None:
             with tempfile.NamedTemporaryFile() as f:
                 annotation_path = Path(f.name)
@@ -634,23 +795,9 @@ def main(self):
                     dest_path=str(annotation_path),
                     is_latest=args.latest,
                 )
-                main_obj.print_annotation_counter_csv(
-                    project_id=project_id,
-                    annotation_path=annotation_path,
-                    group_by=group_by,
-                    output_dir=output_dir,
-                    target_task_ids=task_id_list,
-                    task_query=task_query,
-                )
+                func(annotation_path=annotation_path)
         else:
-            main_obj.print_annotation_counter_csv(
-                project_id=project_id,
-                annotation_path=annotation_path,
-                group_by=group_by,
-                output_dir=output_dir,
-                target_task_ids=task_id_list,
-                task_query=task_query,
-            )
+            func(annotation_path=annotation_path)
 
 
 def parse_args(parser: argparse.ArgumentParser):
@@ -660,7 +807,6 @@ def parse_args(parser: argparse.ArgumentParser):
     parser.add_argument(
         "--annotation", type=str, help="アノテーションzip、またはzipを展開したディレクトリを指定します。" "指定しない場合はAnnoFabからダウンロードします。"
     )
-    parser.add_argument("-o", "--output_dir", type=Path, required=True, help="出力ディレクトリのパス")
 
     parser.add_argument(
         "--group_by",
@@ -670,6 +816,24 @@ def parse_args(parser: argparse.ArgumentParser):
         help="アノテーションの個数をどの単位で集約するかを指定してます。",
     )
 
+    parser.add_argument(
+        "--type",
+        type=str,
+        choices=[e.value for e in CsvType],
+        default=CsvType.LABEL,
+        help="出力するCSVの種類を指定してください。 ``--format csv`` を指定したときのみ有効なオプションです。\n"
+        "\n"
+        "* label: ラベルごとにアノテーション数が記載されているCSV\n"
+        "* attribute: 属性値ごとにアノテーション数が記載されているCSV",
+    )
+
+    argument_parser.add_format(
+        choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
+        default=FormatArgument.CSV,
+    )
+
+    argument_parser.add_output()
+
     parser.add_argument(
         "-tq",
         "--task_query",
diff --git a/annofabcli/statistics/visualize_annotation_count.py b/annofabcli/statistics/visualize_annotation_count.py
index 82a81bdd..f4a37cfc 100644
--- a/annofabcli/statistics/visualize_annotation_count.py
+++ b/annofabcli/statistics/visualize_annotation_count.py
@@ -46,7 +46,7 @@ def plot_label_histogram(
     target_labels: Optional[list[Any]] = None,
     bins: int = 20,
 ):
-    df = pandas.DataFrame([e.labels_counter for e in counter_list])
+    df = pandas.DataFrame([e.annotation_count_by_label for e in counter_list])
     if target_labels is not None:
         df = df[target_labels]
     df.fillna(0, inplace=True)
@@ -95,7 +95,7 @@ def plot_attribute_histogram(
     target_attributes: Optional[list[AttributesKey]] = None,
     bins: int = 20,
 ):
-    df = pandas.DataFrame([e.attributes_counter for e in counter_list])
+    df = pandas.DataFrame([e.annotation_count_by_attribute for e in counter_list])
     if target_attributes is not None:
         df = df[target_attributes]
     df.fillna(0, inplace=True)
diff --git a/docs/command_reference/statistics/list_annotation_count.rst b/docs/command_reference/statistics/list_annotation_count.rst
index a879694c..ce9caee2 100644
--- a/docs/command_reference/statistics/list_annotation_count.rst
+++ b/docs/command_reference/statistics/list_annotation_count.rst
@@ -15,20 +15,54 @@ Examples
 基本的な使い方
 --------------------------
 
-アノテーションzipをダウンロードして、アノテーション数が記載されたCSVファイルを出力します。
-
+ラベルごと/属性値ごとのアノテーション数が記載されたファイルを出力します。
+アノテーション数は、ダウンロードしたアノテーションZIPから算出します。
 
 .. code-block::
 
-    $ annofabcli statistics list_annotation_count --project_id prj1 --output_dir out_dir/
-
-
-.. code-block::
+    $ annofabcli statistics list_annotation_count --project_id prj1 --output out.json --format pretty_json
+
+
+.. code-block:: json
+    :caption: out.json
+
+    [
+    {
+        "annotation_count": 130,
+        "annotation_count_by_label": {
+            "car": 60,
+            "bike": 10,
+        },
+        "annotation_count_by_attribute": {
+            "car": {
+                "occlusion": {
+                    "false": 10,
+                    "true": 20
+                },
+                "type": {
+                    "normal": 10,
+                    "bus":20
+                }
+            },
+            "bike": {
+                "occlusion": {
+                    "false": 10,
+                    "true": 20
+                }
+            }
+        },
+        "task_id": "task1",
+        "status": "complete",
+        "phase": "acceptance",
+        "phase_stage": 1,
+        "input_data_count": 10
+    }
+    ]  
+
+
+* ``annotation_count_by_label`` : ラベルごとのアノテーション数（ ``{label_name: annotation_count}``）
+* ``annotation_count_by_attribute`` : 属性値ごとのアノテーション数（ ``{label_name: {attribute_name: {attribute_value: annotation_count}}}``）
 
-    out_dir/ 
-    ├── labels_count.csv                ラベルごとのアノテーション数が記載されたCSV
-    ├── attributes_count.csv            属性ごとのアノテーション数が記載されたCSV
-    │
 
 集計対象の属性の種類は以下の通りです。
 
@@ -37,13 +71,7 @@ Examples
 * チェックボックス
 
 
-デフォルトではタスクごとにアノテーション数を集計します。入力データごとに集計する場合は、 ``--group_by input_data_id`` を指定してください。
-
-.. code-block::
-
-    $ annofabcli statistics list_annotation_count --project_id prj1 --output_dir out_dir/ \
-    --group_by input_data_id
-
+デフォルトではタスク単位でアノテーション数を集計します。入力データ単位に集計する場合は、 ``--group_by input_data_id`` を指定してください。
 
 ``--annotation`` にアノテーションzipまたはzipを展開したディレクトリを指定できます。
 
@@ -53,87 +81,178 @@ Examples
     --annotation annotation.zip
 
 
+CSV出力
+--------------------------
+
+CSVを出力する場合は、``--type`` でラベルごとのアノテーション数（``--type label``）か、属性値ごとのアノテーション数（``--type attribute``）かを指定してください。
+デフォルトでは、ラベルごとのアノテーション数が出力されます。
+
+
 
 出力結果
 =================================
 
-タスクごとにアノテーション数を集計
+
+JSON出力
 ----------------------------------------------
 
-.. code-block::
+タスクごとのアノテーション数
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-    $ annofabcli statistics list_annotation_count --project_id prj1 --output_dir out_by_task/ \
-    --group_by task_id
+.. code-block::
 
-`out_by_task <https://github.com/kurusugawa-computer/annofab-cli/blob/main/docs/command_reference/statistics/list_annotation_count/out_by_task>`_
+    $ annofabcli statistics list_annotation_count --project_id prj1 --group_by task_id \
+    --format pretty_json --output out_by_task.json 
+
+
+.. code-block:: json
+    :caption: out_by_task.json
+
+    [
+    {
+        "annotation_count": 130,
+        "annotation_count_by_label": {
+            "car": 60,
+            "bike": 10,
+        },
+        "annotation_count_by_attribute": {
+            "car": {
+                "occlusion": {
+                    "false": 10,
+                    "true": 20
+                },
+                "type": {
+                    "normal": 10,
+                    "bus":20
+                }
+            },
+            "bike": {
+                "occlusion": {
+                    "false": 10,
+                    "true": 20
+                }
+            }
+        },
+        "task_id": "task1",
+        "status": "complete",
+        "phase": "acceptance",
+        "phase_stage": 1,
+        "input_data_count": 10
+    }
+    ]  
+
+
+入力データごとのアノテーション数
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 .. code-block::
 
-    out_by_task/
-    ├── labels_count.csv
-    ├── attributes_count.csv
+    $ annofabcli statistics list_annotation_count --project_id prj1 --group_by input_data_id \
+    --format pretty_json --output out_by_input_data.json 
+
+
+.. code-block:: json
+    :caption: out_by_input_data.json
+
+    [
+    {
+        "annotation_count": 130,
+        "annotation_count_by_label": {
+            "car": 60,
+            "bike": 10,
+        },
+        "annotation_count_by_attribute": {
+            "car": {
+                "occlusion": {
+                    "false": 10,
+                    "true": 20
+                },
+                "type": {
+                    "normal": 10,
+                    "bus":20
+                }
+            },
+            "bike": {
+                "occlusion": {
+                    "false": 10,
+                    "true": 20
+                }
+            }
+        },
+        "task_id": "task1",
+        "status": "complete",
+        "phase": "acceptance",
+        "phase_stage": 1,
+        "input_data_id": "input1",
+        "input_data_name": "input1"
+    }
+    ]  
+
+
+CSV出力
+----------------------------------------------
 
+タスクごとラベルごとのアノテーション数
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
-.. csv-table:: labels_count.csv
-   :header: task_id,task_status,task_phase,task_phase_stage,input_data_count,Cat,...
+.. code-block::
 
-    task1,break,annotation,1,5,20,...
-    task2,complete,acceptance,1,5,12,...
+    $ annofabcli statistics list_annotation_count --project_id prj1 --group_by task_id \
+    --format csv --type label --output out_by_task_label.csv 
 
 
+.. csv-table:: out_by_task_label.csv 
+    :header-rows: 1
+    :file: list_annotation_count/out_by_task_label.csv
 
-.. csv-table:: attributes_count.csv
-    :header-rows: 3
-    
-    ,,,,,Cat,Cat,...
-    ,,,,,occluded,occluded,...
-    task_id,task_status,task_phase,task_phase_stage,input_data_count,True,False,...
-    task1,break,acceptance,1,5,2,0,...
-    task2,complete,acceptance,1,5,2,0,...
 
+タスクごと属性ごとのアノテーション数
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+.. code-block::
 
+    $ annofabcli statistics list_annotation_count --project_id prj1 --group_by task_id \
+    --format csv --type attribute --output out_by_task_attribute.csv 
 
 
-入力データごとにアノテーション数を集計
-----------------------------------------------
+.. csv-table:: out_by_task_attribute.csv 
+    :header-rows: 1
+    :file: list_annotation_count/out_by_task_attribute.csv
 
 
-.. code-block::
+入力データごとラベルごとのアノテーション数
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-    $ annofabcli statistics list_annotation_count --project_id prj1 --output_dir out_by_input_data/ \
-    --group_by input_data_id
 
-`out_by_input_data <https://github.com/kurusugawa-computer/annofab-cli/blob/main/docs/command_reference/statistics/list_annotation_count/out_by_input_data>`_
+.. code-block::
 
+    $ annofabcli statistics list_annotation_count --project_id prj1 --group_by input_data_id \
+    --format csv --type label --output out_by_input_data_label.csv 
 
-.. code-block::
 
-    out_by_input_data/
-    ├── labels_count.csv
-    ├── attributes_count.csv
+.. csv-table:: out_by_input_data_label.csv 
+    :header-rows: 1
+    :file: list_annotation_count/out_by_input_data_label.csv
 
 
+入力データごと属性ごとのアノテーション数
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-.. csv-table:: labels_count.csv
-   :header: input_data_id,input_data_name,task_id,task_status,task_phase,task_phase_stage,label_Cat,...
+.. code-block::
+
+    $ annofabcli statistics list_annotation_count --project_id prj1 --group_by input_data_id \
+    --format csv --type attribute --output out_by_input_data_attribute.csv 
 
-    input1,input1,task1,break,acceptance,1,12
-    input2,input2,task2,complete,acceptance,1,12
 
+.. csv-table:: out_by_input_data_attribute.csv
+    :header-rows: 1
+    :file: list_annotation_count/out_by_input_data_attribute.csv
 
 
 
-.. csv-table:: attributes_count.csv
-    :header-rows: 3
 
-    ,,,,,,Cat,Cat,...
-    ,,,,,,occluded,occluded,...
-    input_data_id,input_data_name,task_id,task_status,task_phase,task_phase_stage,True,False,...
-    input1,input1,task1,break,acceptance,1,10,5,...
-    input2,input2,task2,complete,acceptance,1,10,5,...
 
 Usage Details
 =================================
@@ -143,3 +262,4 @@ Usage Details
    :prog: annofabcli statistics list_annotation_count
    :nosubcommands:
    :nodefaultconst:
+
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_input_data/attirbutes_count.csv b/docs/command_reference/statistics/list_annotation_count/out_by_input_data/attirbutes_count.csv
deleted file mode 100644
index a0af9938..00000000
--- a/docs/command_reference/statistics/list_annotation_count/out_by_input_data/attirbutes_count.csv
+++ /dev/null
@@ -1,7 +0,0 @@
-﻿,,,,,,Cat,Cat,climatic,climatic,climatic,climatic,climatic,climatic
-,,,,,,occluded,occluded,weather,weather,temparature,temparature,temparature,temparature
-input_data_id,input_data_name,task_id,task_status,task_phase,task_phase_stage,True,False,sunny,rainy,10,20,30,40
-0733d1e1-ef85-455e-aec0-ff05c499b711,./tests/data/lenna.png,sample_0,break,acceptance,1,1,0,1,0,0,1,0,0
-c86205d1-bdd4-4110-ae46-194e661d622b,./tests/data/lenna.png,sample_1,complete,acceptance,1,1,0,1,0,0,1,0,0
-a3281975-e632-47a7-a71f-08013fad5604,./tests/data/lenna.png,sample_0,break,acceptance,1,1,0,1,0,0,1,0,0
-c6e1c2ec-6c7c-41c6-9639-4244c2ed2839,./tests/data/lenna.png,sample_1,complete,acceptance,1,1,0,1,0,0,1,0,0
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_input_data/labels_count.csv b/docs/command_reference/statistics/list_annotation_count/out_by_input_data/labels_count.csv
deleted file mode 100644
index 7264b63a..00000000
--- a/docs/command_reference/statistics/list_annotation_count/out_by_input_data/labels_count.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-﻿input_data_id,input_data_name,task_id,task_status,task_phase,task_phase_stage,Cat,leg,eye,dog,human,bird,climatic
-0733d1e1-ef85-455e-aec0-ff05c499b711,./tests/data/lenna.png,sample_0,break,acceptance,1,1,1,1,1,1,1,1
-c86205d1-bdd4-4110-ae46-194e661d622b,./tests/data/lenna.png,sample_1,complete,acceptance,1,1,1,1,1,1,1,1
-a3281975-e632-47a7-a71f-08013fad5604,./tests/data/lenna.png,sample_0,break,acceptance,1,1,1,1,1,1,1,1
-c6e1c2ec-6c7c-41c6-9639-4244c2ed2839,./tests/data/lenna.png,sample_1,complete,acceptance,1,1,1,1,1,1,1,1
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_input_data_attribute.csv b/docs/command_reference/statistics/list_annotation_count/out_by_input_data_attribute.csv
new file mode 100644
index 00000000..00a8a104
--- /dev/null
+++ b/docs/command_reference/statistics/list_annotation_count/out_by_input_data_attribute.csv
@@ -0,0 +1,4 @@
+task_id,status,phase,phase_stage,input_data_id,input_data_name,annotation_count,car,car
+,,,,,,,occlusion,occlusion
+,,,,,,,true,false
+task2,complete,acceptance,1,input2,input2,80,12,5
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_input_data_label.csv b/docs/command_reference/statistics/list_annotation_count/out_by_input_data_label.csv
new file mode 100644
index 00000000..35cbaad5
--- /dev/null
+++ b/docs/command_reference/statistics/list_annotation_count/out_by_input_data_label.csv
@@ -0,0 +1,3 @@
+task_id,status,phase,phase_stage,input_data_id,input_data_name,annotation_count,car,bike
+task1,break,annotation,1,input1,input1,100,20,10
+task2,complete,acceptance,1,input2,input2,80,12,5
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_task/attirbutes_count.csv b/docs/command_reference/statistics/list_annotation_count/out_by_task/attirbutes_count.csv
deleted file mode 100644
index bf8f5728..00000000
--- a/docs/command_reference/statistics/list_annotation_count/out_by_task/attirbutes_count.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-﻿,,,,Cat,Cat,climatic,climatic,climatic,climatic,climatic,climatic
-,,,,occluded,occluded,weather,weather,temparature,temparature,temparature,temparature
-task_id,task_status,task_phase,task_phase_stage,True,False,sunny,rainy,10,20,30,40
-sample_0,break,acceptance,1,2,0,2,0,0,2,0,0
-sample_1,complete,acceptance,1,2,0,2,0,0,2,0,0
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_task/labels_count.csv b/docs/command_reference/statistics/list_annotation_count/out_by_task/labels_count.csv
deleted file mode 100644
index 6d921436..00000000
--- a/docs/command_reference/statistics/list_annotation_count/out_by_task/labels_count.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-﻿task_id,task_status,task_phase,task_phase_stage,Cat,leg,eye,dog,human,bird,climatic
-sample_0,break,acceptance,1,2,2,2,2,2,2,2
-sample_1,complete,acceptance,1,2,2,2,2,2,2,2
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_task_attribute.csv b/docs/command_reference/statistics/list_annotation_count/out_by_task_attribute.csv
new file mode 100644
index 00000000..34e7e55d
--- /dev/null
+++ b/docs/command_reference/statistics/list_annotation_count/out_by_task_attribute.csv
@@ -0,0 +1,4 @@
+task_id,status,phase,phase_stage,input_data_count,annotation_count,car,car
+,,,,,,occlusion,occlusion
+,,,,,,true,false
+task2,complete,acceptance,1,10,80,12,5
diff --git a/docs/command_reference/statistics/list_annotation_count/out_by_task_label.csv b/docs/command_reference/statistics/list_annotation_count/out_by_task_label.csv
new file mode 100644
index 00000000..e6cebbec
--- /dev/null
+++ b/docs/command_reference/statistics/list_annotation_count/out_by_task_label.csv
@@ -0,0 +1,3 @@
+task_id,status,phase,phase_stage,input_data_count,annotation_count,car,bike
+task1,break,annotation,1,5,100,20,10
+task2,complete,acceptance,1,5,80,12,5
diff --git a/poetry.lock b/poetry.lock
index 7b4f2f4a..3eed1ef0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -127,29 +127,25 @@ lxml = ["lxml"]
 
 [[package]]
 name = "black"
-version = "21.12b0"
+version = "22.1.0"
 description = "The uncompromising code formatter."
 category = "dev"
 optional = false
 python-versions = ">=3.6.2"
 
 [package.dependencies]
-click = ">=7.1.2"
+click = ">=8.0.0"
 mypy-extensions = ">=0.4.3"
-pathspec = ">=0.9.0,<1"
+pathspec = ">=0.9.0"
 platformdirs = ">=2"
-tomli = ">=0.2.6,<2.0.0"
+tomli = ">=1.1.0"
 typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
-typing-extensions = [
-    {version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
-    {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
-]
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
 
 [package.extras]
 colorama = ["colorama (>=0.4.3)"]
 d = ["aiohttp (>=3.7.4)"]
 jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-python2 = ["typed-ast (>=1.4.3)"]
 uvloop = ["uvloop (>=0.15.2)"]
 
 [[package]]
@@ -190,7 +186,7 @@ unicode_backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
-version = "8.0.3"
+version = "8.0.4"
 description = "Composable command line interface toolkit"
 category = "dev"
 optional = false
@@ -453,7 +449,7 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "lxml"
-version = "4.7.1"
+version = "4.8.0"
 description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
 category = "main"
 optional = false
@@ -478,11 +474,11 @@ altgraph = ">=0.15"
 
 [[package]]
 name = "markupsafe"
-version = "2.0.1"
+version = "2.1.0"
 description = "Safely add untrusted strings to HTML/XML markup."
 category = "main"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "marshmallow"
@@ -1112,11 +1108,11 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "tomli"
-version = "1.2.3"
+version = "2.0.1"
 description = "A lil' TOML parser"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "tornado"
@@ -1244,7 +1240,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7.1"
-content-hash = "33656c304aaf35ea9aed9d24421e06cbf92dcb206b626d7ce616be4041b103c7"
+content-hash = "ef25d411fcc4ffe1757e0afd1922868cd78082f0646879ed52027afb7cc74798"
 
 [metadata.files]
 alabaster = [
@@ -1295,8 +1291,29 @@ beautifulsoup4 = [
     {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"},
 ]
 black = [
-    {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
-    {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"},
+    {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
+    {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
+    {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
+    {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
+    {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
+    {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
+    {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
+    {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
+    {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
+    {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
+    {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
+    {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
+    {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
+    {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
+    {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
+    {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
+    {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
+    {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
+    {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
+    {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
+    {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
+    {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
+    {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
 ]
 bokeh = [
     {file = "bokeh-2.4.2-py3-none-any.whl", hash = "sha256:2a842d717feeee802e668054277c09054b6f1561557a16dddaf5f7c452f2728c"},
@@ -1311,8 +1328,8 @@ charset-normalizer = [
     {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
 ]
 click = [
-    {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
-    {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
+    {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
+    {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@@ -1472,141 +1489,113 @@ lazy-object-proxy = [
     {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
 ]
 lxml = [
-    {file = "lxml-4.7.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f"},
-    {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e"},
-    {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17"},
-    {file = "lxml-4.7.1-cp27-cp27m-win32.whl", hash = "sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419"},
-    {file = "lxml-4.7.1-cp27-cp27m-win_amd64.whl", hash = "sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2"},
-    {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5"},
-    {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d"},
-    {file = "lxml-4.7.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc"},
-    {file = "lxml-4.7.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e"},
-    {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3"},
-    {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175"},
-    {file = "lxml-4.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6"},
-    {file = "lxml-4.7.1-cp310-cp310-win32.whl", hash = "sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6"},
-    {file = "lxml-4.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e"},
-    {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675"},
-    {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7"},
-    {file = "lxml-4.7.1-cp35-cp35m-win32.whl", hash = "sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5"},
-    {file = "lxml-4.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03"},
-    {file = "lxml-4.7.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b"},
-    {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55"},
-    {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d"},
-    {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d"},
-    {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6"},
-    {file = "lxml-4.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d"},
-    {file = "lxml-4.7.1-cp36-cp36m-win32.whl", hash = "sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d"},
-    {file = "lxml-4.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a"},
-    {file = "lxml-4.7.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8"},
-    {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868"},
-    {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a"},
-    {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986"},
-    {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3"},
-    {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944"},
-    {file = "lxml-4.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b"},
-    {file = "lxml-4.7.1-cp37-cp37m-win32.whl", hash = "sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4"},
-    {file = "lxml-4.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1"},
-    {file = "lxml-4.7.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"},
-    {file = "lxml-4.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e"},
-    {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440"},
-    {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f"},
-    {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e"},
-    {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e"},
-    {file = "lxml-4.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9"},
-    {file = "lxml-4.7.1-cp38-cp38-win32.whl", hash = "sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd"},
-    {file = "lxml-4.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d"},
-    {file = "lxml-4.7.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9"},
-    {file = "lxml-4.7.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489"},
-    {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36"},
-    {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42"},
-    {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db"},
-    {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851"},
-    {file = "lxml-4.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4"},
-    {file = "lxml-4.7.1-cp39-cp39-win32.whl", hash = "sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0"},
-    {file = "lxml-4.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2"},
-    {file = "lxml-4.7.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4"},
-    {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f"},
-    {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a"},
-    {file = "lxml-4.7.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7"},
-    {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267"},
-    {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60"},
-    {file = "lxml-4.7.1.tar.gz", hash = "sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24"},
+    {file = "lxml-4.8.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b"},
+    {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430"},
+    {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a"},
+    {file = "lxml-4.8.0-cp27-cp27m-win32.whl", hash = "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5"},
+    {file = "lxml-4.8.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9"},
+    {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc"},
+    {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170"},
+    {file = "lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9"},
+    {file = "lxml-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03"},
+    {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe"},
+    {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa"},
+    {file = "lxml-4.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1"},
+    {file = "lxml-4.8.0-cp310-cp310-win32.whl", hash = "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b"},
+    {file = "lxml-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76"},
+    {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6"},
+    {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2"},
+    {file = "lxml-4.8.0-cp35-cp35m-win32.whl", hash = "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150"},
+    {file = "lxml-4.8.0-cp35-cp35m-win_amd64.whl", hash = "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654"},
+    {file = "lxml-4.8.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169"},
+    {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb"},
+    {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3"},
+    {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4"},
+    {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e"},
+    {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613"},
+    {file = "lxml-4.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33"},
+    {file = "lxml-4.8.0-cp36-cp36m-win32.whl", hash = "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429"},
+    {file = "lxml-4.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63"},
+    {file = "lxml-4.8.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a"},
+    {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4"},
+    {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15"},
+    {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f"},
+    {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c"},
+    {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85"},
+    {file = "lxml-4.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141"},
+    {file = "lxml-4.8.0-cp37-cp37m-win32.whl", hash = "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63"},
+    {file = "lxml-4.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8"},
+    {file = "lxml-4.8.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7"},
+    {file = "lxml-4.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428"},
+    {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5"},
+    {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f"},
+    {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870"},
+    {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9"},
+    {file = "lxml-4.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68"},
+    {file = "lxml-4.8.0-cp38-cp38-win32.whl", hash = "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696"},
+    {file = "lxml-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939"},
+    {file = "lxml-4.8.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807"},
+    {file = "lxml-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1"},
+    {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939"},
+    {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca"},
+    {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c"},
+    {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87"},
+    {file = "lxml-4.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9"},
+    {file = "lxml-4.8.0-cp39-cp39-win32.whl", hash = "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea"},
+    {file = "lxml-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c"},
+    {file = "lxml-4.8.0-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507"},
+    {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9"},
+    {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e"},
+    {file = "lxml-4.8.0-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0"},
+    {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79"},
+    {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93"},
+    {file = "lxml-4.8.0.tar.gz", hash = "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23"},
 ]
 macholib = [
     {file = "macholib-1.15.2-py2.py3-none-any.whl", hash = "sha256:885613dd02d3e26dbd2b541eb4cc4ce611b841f827c0958ab98656e478b9e6f6"},
     {file = "macholib-1.15.2.tar.gz", hash = "sha256:1542c41da3600509f91c165cb897e7e54c0e74008bd8da5da7ebbee519d593d2"},
 ]
 markupsafe = [
-    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
-    {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
-    {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3028252424c72b2602a323f70fbf50aa80a5d3aa616ea6add4ba21ae9cc9da4c"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:290b02bab3c9e216da57c1d11d2ba73a9f73a614bbdcc027d299a60cdfabb11a"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e104c0c2b4cd765b4e83909cde7ec61a1e313f8a75775897db321450e928cce"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24c3be29abb6b34052fd26fc7a8e0a49b1ee9d282e3665e8ad09a0a68faee5b3"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204730fd5fe2fe3b1e9ccadb2bd18ba8712b111dcabce185af0b3b5285a7c989"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3b64c65328cb4cd252c94f83e66e3d7acf8891e60ebf588d7b493a55a1dbf26"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96de1932237abe0a13ba68b63e94113678c379dca45afa040a17b6e1ad7ed076"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75bb36f134883fdbe13d8e63b8675f5f12b80bb6627f7714c7d6c5becf22719f"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-win32.whl", hash = "sha256:4056f752015dfa9828dce3140dbadd543b555afb3252507348c493def166d454"},
+    {file = "MarkupSafe-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d4e702eea4a2903441f2735799d217f4ac1b55f7d8ad96ab7d4e25417cb0827c"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f0eddfcabd6936558ec020130f932d479930581171368fd728efcfb6ef0dd357"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ddea4c352a488b5e1069069f2f501006b1a4362cb906bee9a193ef1245a7a61"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09c86c9643cceb1d87ca08cdc30160d1b7ab49a8a21564868921959bd16441b8"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a0abef2ca47b33fb615b491ce31b055ef2430de52c5b3fb19a4042dbc5cadb"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:736895a020e31b428b3382a7887bfea96102c529530299f426bf2e636aacec9e"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:679cbb78914ab212c49c67ba2c7396dc599a8479de51b9a87b174700abd9ea49"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:84ad5e29bf8bab3ad70fd707d3c05524862bddc54dc040982b0dbcff36481de7"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-win32.whl", hash = "sha256:8da5924cb1f9064589767b0f3fc39d03e3d0fb5aa29e0cb21d43106519bd624a"},
+    {file = "MarkupSafe-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:454ffc1cbb75227d15667c09f164a0099159da0c1f3d2636aa648f12675491ad"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:142119fb14a1ef6d758912b25c4e803c3ff66920635c44078666fe7cc3f8f759"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2a5a856019d2833c56a3dcac1b80fe795c95f401818ea963594b345929dffa7"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1fb9b2eec3c9714dd936860850300b51dbaa37404209c8d4cb66547884b7ed"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62c0285e91414f5c8f621a17b69fc0088394ccdaa961ef469e833dbff64bd5ea"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc3150f85e2dbcf99e65238c842d1cfe69d3e7649b19864c1cc043213d9cd730"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f02cf7221d5cd915d7fa58ab64f7ee6dd0f6cddbb48683debf5d04ae9b1c2cc1"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5653619b3eb5cbd35bfba3c12d575db2a74d15e0e1c08bf1db788069d410ce8"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d2f5d97fcbd004c03df8d8fe2b973fe2b14e7bfeb2cfa012eaa8759ce9a762f"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-win32.whl", hash = "sha256:3cace1837bc84e63b3fd2dfce37f08f8c18aeb81ef5cf6bb9b51f625cb4e6cd8"},
+    {file = "MarkupSafe-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fabbe18087c3d33c5824cb145ffca52eccd053061df1d79d4b66dafa5ad2a5ea"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:023af8c54fe63530545f70dd2a2a7eed18d07a9a77b94e8bf1e2ff7f252db9a3"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d66624f04de4af8bbf1c7f21cc06649c1c69a7f84109179add573ce35e46d448"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c532d5ab79be0199fa2658e24a02fce8542df196e60665dd322409a03db6a52c"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ec74fada3841b8c5f4c4f197bea916025cb9aa3fe5abf7d52b655d042f956"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c653fde75a6e5eb814d2a0a89378f83d1d3f502ab710904ee585c38888816c"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:961eb86e5be7d0973789f30ebcf6caab60b844203f4396ece27310295a6082c7"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:598b65d74615c021423bd45c2bc5e9b59539c875a9bdb7e5f2a6b92dfcfc268d"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:599941da468f2cf22bf90a84f6e2a65524e87be2fce844f96f2dd9a6c9d1e635"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-win32.whl", hash = "sha256:e6f7f3f41faffaea6596da86ecc2389672fa949bd035251eab26dc6697451d05"},
+    {file = "MarkupSafe-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:b8811d48078d1cf2a6863dafb896e68406c5f513048451cd2ded0473133473c7"},
+    {file = "MarkupSafe-2.1.0.tar.gz", hash = "sha256:80beaf63ddfbc64a0452b841d8036ca0611e049650e20afcb882f5d3c266d65f"},
 ]
 marshmallow = [
     {file = "marshmallow-3.14.1-py3-none-any.whl", hash = "sha256:04438610bc6dadbdddb22a4a55bcc7f6f8099e69580b2e67f5a681933a1f4400"},
@@ -1956,8 +1945,8 @@ toml = [
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 tomli = [
-    {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
-    {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
+    {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
 ]
 tornado = [
     {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"},
diff --git a/pyproject.toml b/pyproject.toml
index 1b79fd6c..061d4ba9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "annofabcli"
-version = "1.57.2"
+version = "1.57.3"
 description = "Utility Command Line Interface for AnnoFab"
 authors = ["yuji38kwmt"]
 license = "MIT"
@@ -47,7 +47,7 @@ pytest-cov = "*"
 # format library
 isort = "*"
 autoflake = "*"
-black = {version = "^21.12b0", allow-prereleases = true}
+black = "*"
 
 # lint library
 flake8 = "*"


Test patch:
diff --git a/tests/test_local_statistics.py b/tests/test_local_statistics.py
index ad569c75..f0c77d38 100644
--- a/tests/test_local_statistics.py
+++ b/tests/test_local_statistics.py
@@ -459,23 +459,23 @@ def test_get_annotation_counter(self):
         counter = ListAnnotationCounterByInputData.get_annotation_counter(annotation)
         assert counter.input_data_id == "input1"
         assert counter.task_id == "task1"
-        assert counter.labels_counter == collections.Counter({"bird": 2, "climatic": 1})
-        assert counter.attributes_counter == collections.Counter(
+        assert counter.annotation_count_by_label == collections.Counter({"bird": 2, "climatic": 1})
+        assert counter.annotation_count_by_attribute == collections.Counter(
             {
                 ("bird", "weight", "4"): 1,
                 ("bird", "weight", "3"): 1,
-                ("bird", "occluded", "True"): 2,
+                ("bird", "occluded", "true"): 2,
                 ("climatic", "weather", "sunny"): 1,
             }
         )
 
         counter2 = ListAnnotationCounterByInputData.get_annotation_counter(
-            annotation, target_labels=["climatic"], target_attributes=[("bird", "occluded", "True")]
+            annotation, target_labels=["climatic"], target_attributes=[("bird", "occluded", "true")]
         )
-        assert counter2.labels_counter == collections.Counter({"climatic": 1})
-        assert counter2.attributes_counter == collections.Counter(
+        assert counter2.annotation_count_by_label == collections.Counter({"climatic": 1})
+        assert counter2.annotation_count_by_attribute == collections.Counter(
             {
-                ("bird", "occluded", "True"): 2,
+                ("bird", "occluded", "true"): 2,
             }
         )
 
@@ -489,7 +489,7 @@ def test_print_labels_count(self):
         counter_list = ListAnnotationCounterByInputData.get_annotation_counter_list(
             data_path / "simple-annotations.zip"
         )
-        ListAnnotationCounterByInputData.print_labels_count(
+        ListAnnotationCounterByInputData.print_labels_count_csv(
             counter_list,
             output_file=out_path / "list_annotation_count/labels_count_by_input_data.csv",
             label_columns=["dog", "human"],
@@ -499,7 +499,7 @@ def test_print_attributes_count(self):
         counter_list = ListAnnotationCounterByInputData.get_annotation_counter_list(
             data_path / "simple-annotations.zip"
         )
-        ListAnnotationCounterByInputData.print_attributes_count(
+        ListAnnotationCounterByInputData.print_attributes_count_csv(
             counter_list,
             output_file=out_path / "list_annotation_count/attributes_count_by_input_data.csv",
             attribute_columns=[("Cat", "occluded", "True"), ("climatic", "temparature", "20")],
@@ -511,20 +511,20 @@ def test_get_annotation_counter_list(self):
         counter_list = ListAnnotationCounterByTask.get_annotation_counter_list(data_path / "simple-annotations.zip")
         assert len(counter_list) == 2
 
-    def test_print_labels_count(self):
+    def test_print_labels_count_csv(self):
         counter_list = ListAnnotationCounterByTask.get_annotation_counter_list(data_path / "simple-annotations.zip")
-        ListAnnotationCounterByTask.print_labels_count(
+        ListAnnotationCounterByTask.print_labels_count_csv(
             counter_list,
             output_file=out_path / "list_annotation_count/labels_count_by_task.csv",
             label_columns=["dog", "human"],
         )
 
-    def test_print_attributes_count(self):
+    def test_print_attributes_count_csv(self):
         counter_list = ListAnnotationCounterByTask.get_annotation_counter_list(data_path / "simple-annotations.zip")
-        ListAnnotationCounterByTask.print_attributes_count(
+        ListAnnotationCounterByTask.print_attributes_count_csv(
             counter_list,
             output_file=out_path / "list_annotation_count/attributes_count_by_task.csv",
-            attribute_columns=[("Cat", "occluded", "True"), ("climatic", "temparature", "20")],
+            attribute_columns=[("Cat", "occluded", "true"), ("climatic", "temparature", "20")],
         )
 
 
diff --git a/tests/test_statistics.py b/tests/test_statistics.py
index 604d6d52..04d01b5d 100644
--- a/tests/test_statistics.py
+++ b/tests/test_statistics.py
@@ -21,32 +21,36 @@
 
 class TestCommandLine:
     def test_list_annotation_count_by_task(self):
-        output_dir = str(out_dir / "list_annotation_count_by_task-out")
         main(
             [
                 "statistics",
                 "list_annotation_count",
                 "--project_id",
                 project_id,
-                "--output_dir",
-                output_dir,
+                "--output",
+                str(out_dir / "list_annotation_count_by_task-out.json"),
                 "--group_by",
                 "task_id",
+                "--format",
+                "pretty_json",
             ]
         )
 
     def test_list_annotation_count_by_input_data(self):
-        output_dir = str(out_dir / "list_annotation_count_by_input_data-out")
         main(
             [
                 "statistics",
                 "list_annotation_count",
                 "--project_id",
                 project_id,
-                "--output_dir",
-                output_dir,
+                "--output",
+                str(out_dir / "list_annotation_count_by_input_data-out.csv"),
                 "--group_by",
-                "task_id",
+                "input_data_id",
+                "--type",
+                "attribute",
+                "--format",
+                "csv",
             ]
         )