From 75e5ffaeab818b02e8815a4222fe43d795cce953 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Thu, 6 Jun 2024 14:29:29 +0200 Subject: [PATCH 1/2] Revert "MNT space creator only after merge" This reverts commit 3803876655d76d84f7287358de3eb272e380ea9b. --- .github/workflows/deploy-model-card-creator.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-model-card-creator.yml b/.github/workflows/deploy-model-card-creator.yml index f4c94f21..258269e8 100644 --- a/.github/workflows/deploy-model-card-creator.yml +++ b/.github/workflows/deploy-model-card-creator.yml @@ -2,6 +2,7 @@ name: Deploy-Space-Creator on: - push + - pull_request concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} From 20e8a84dceb99a03933eb2547bfafd50766d5b71 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Wed, 19 Jun 2024 11:48:39 +0200 Subject: [PATCH 2/2] MNT improve typehints --- skops/card/_markup.py | 4 ++-- skops/card/_model_card.py | 14 +++++++------- skops/cli/_convert.py | 3 +-- skops/hub_utils/_hf_hub.py | 34 ++++++++++++++++------------------ skops/io/_audit.py | 20 ++++++++++---------- skops/io/_general.py | 30 +++++++++++++++--------------- skops/io/_numpy.py | 12 ++++++------ skops/io/_persist.py | 6 +++--- skops/io/_quantile_forest.py | 4 ++-- skops/io/_scipy.py | 4 ++-- skops/io/_sklearn.py | 10 +++++----- skops/io/_utils.py | 6 ++++-- skops/io/_visualize.py | 4 ++-- skops/io/old/_general_v0.py | 4 ++-- skops/io/old/_numpy_v0.py | 4 ++-- 15 files changed, 79 insertions(+), 80 deletions(-) diff --git a/skops/card/_markup.py b/skops/card/_markup.py index 2e42a25c..39df0340 100644 --- a/skops/card/_markup.py +++ b/skops/card/_markup.py @@ -5,7 +5,7 @@ import sys from collections.abc import Mapping from contextlib import contextmanager -from typing import Any, Sequence +from typing import Any from skops.card._model_card import TableSection @@ -258,7 +258,7 @@ def _table(self, item) -> str: # pandoc < 2.5 columns, body = self._table_old(item) - table: Mapping[str, Sequence[Any]] + table: Mapping[str, list[Any]] if not body: table = {key: [] for key in columns} else: diff --git a/skops/card/_model_card.py b/skops/card/_model_card.py index 31633985..caeb0a4e 100644 --- a/skops/card/_model_card.py +++ b/skops/card/_model_card.py @@ -12,7 +12,7 @@ from hashlib import sha256 from pathlib import Path from reprlib import Repr -from typing import Any, Iterator, List, Literal, Optional, Sequence, Union +from typing import Any, Iterator, Literal import joblib from huggingface_hub import ModelCardData @@ -60,7 +60,7 @@ def _clean_table(table: str) -> str: return table -def metadata_from_config(config_path: Union[str, Path]) -> ModelCardData: +def metadata_from_config(config_path: str | Path) -> ModelCardData: """Construct a ``ModelCardData`` object from a ``config.json`` file. Most information needed for the metadata section of a ``README.md`` file on @@ -281,7 +281,7 @@ def __repr__(self) -> str: class TableSection(Section): """Adds a table to the model card""" - table: Mapping[str, Sequence[Any]] = field(default_factory=dict) + table: Mapping[str, list[Any]] = field(default_factory=dict) folded: bool = False def __post_init__(self) -> None: @@ -488,7 +488,7 @@ def __init__( model_diagram: bool | Literal["auto"] | str = "auto", metadata: ModelCardData | None = None, template: Literal["skops"] | dict[str, str] | None = "skops", - trusted: Optional[List[str]] = None, + trusted: list[str] | None = None, ) -> None: self.model = model self.metadata = metadata or ModelCardData() @@ -619,7 +619,7 @@ def add(self, folded: bool = False, **kwargs: str) -> Self: return self def _select( - self, subsection_names: Sequence[str], create: bool = True + self, subsection_names: list[str], create: bool = True ) -> dict[str, Section]: """Select a single section from the data. @@ -713,7 +713,7 @@ def select(self, key: str) -> Section: parent_section = self._select(subsection_names, create=False) return parent_section[leaf_node_name] - def delete(self, key: str | Sequence[str]) -> None: + def delete(self, key: str | list[str]) -> None: """Delete a section from the model card. To delete a subsection of an existing section, use a ``"/"`` in the @@ -1181,7 +1181,7 @@ def add_metrics( def add_permutation_importances( self, permutation_importances, - columns: Sequence[str], + columns: list[str], plot_file: str | Path = "permutation_importances.png", plot_name: str = "Permutation Importances", overwrite: bool = False, diff --git a/skops/cli/_convert.py b/skops/cli/_convert.py index 7d630c15..b02bf5f3 100644 --- a/skops/cli/_convert.py +++ b/skops/cli/_convert.py @@ -5,7 +5,6 @@ import os import pathlib import pickle -from typing import Optional from skops.cli._utils import get_log_level from skops.io import dumps, get_untrusted_types @@ -59,7 +58,7 @@ def _convert_file( def format_parser( - parser: Optional[argparse.ArgumentParser] = None, + parser: argparse.ArgumentParser | None = None, ) -> argparse.ArgumentParser: """Adds arguments and help to parent CLI parser for the convert method.""" diff --git a/skops/hub_utils/_hf_hub.py b/skops/hub_utils/_hf_hub.py index d1d1eff6..3cc64a31 100644 --- a/skops/hub_utils/_hf_hub.py +++ b/skops/hub_utils/_hf_hub.py @@ -11,7 +11,7 @@ import shutil import warnings from pathlib import Path -from typing import Any, List, Literal, MutableMapping, Optional, Sequence, Union +from typing import Any, Literal, MutableMapping import numpy as np from huggingface_hub import HfApi, InferenceClient, snapshot_download @@ -25,7 +25,7 @@ ] -def _validate_folder(path: Union[str, Path]) -> None: +def _validate_folder(path: str | Path) -> None: """Validate the contents of a folder. This function checks if the contents of a folder make a valid repo for a @@ -117,7 +117,7 @@ def _get_example_input_from_tabular_data(data): ) -def _get_example_input_from_text_data(data: Sequence[str]): +def _get_example_input_from_text_data(data: list[str]): """Returns the example input of a model for a text task. The input is converted into a dictionary which is then stored in the config @@ -125,7 +125,7 @@ def _get_example_input_from_text_data(data: Sequence[str]): Parameters ---------- - data: Sequence[str] + data: list of str A sequence of strings. The first 3 elements are used as example input. Returns @@ -197,9 +197,9 @@ def _get_column_names(data): def _create_config( *, - model_path: Union[str, Path], - requirements: List[str], - dst: Union[str, Path], + model_path: str | Path, + requirements: list[str], + dst: str | Path, task: Literal[ "tabular-classification", "tabular-regression", @@ -319,9 +319,9 @@ def _check_model_file(path: str | Path) -> Path: def init( *, - model: Union[str, Path], - requirements: List[str], - dst: Union[str, Path], + model: str | Path, + requirements: list[str], + dst: str | Path, task: Literal[ "tabular-classification", "tabular-regression", @@ -468,9 +468,7 @@ def dump_json(path, content): json.dump(content, f, sort_keys=True, indent=4) -def update_env( - *, path: Union[str, Path], requirements: Union[List[str], None] = None -) -> None: +def update_env(*, path: str | Path, requirements: list[str] | None = None) -> None: """Update the environment requirements of a repo. This function takes the path to the repo, and updates the requirements of @@ -498,7 +496,7 @@ def update_env( def push( *, repo_id: str, - source: Union[str, Path], + source: str | Path, token: str | None = None, commit_message: str | None = None, create_remote: bool = False, @@ -579,7 +577,7 @@ def push( ) -def get_config(path: Union[str, Path]) -> dict[str, Any]: +def get_config(path: str | Path) -> dict[str, Any]: """Returns the configuration of a project. Parameters @@ -598,7 +596,7 @@ def get_config(path: Union[str, Path]) -> dict[str, Any]: return config -def get_requirements(path: Union[str, Path]) -> List[str]: +def get_requirements(path: str | Path) -> list[str]: """Returns the requirements of a project. Parameters @@ -620,7 +618,7 @@ def get_requirements(path: Union[str, Path]) -> List[str]: def download( *, repo_id: str, - dst: Union[str, Path], + dst: str | Path, revision: str | None = None, token: str | None = None, keep_cache: bool = True, @@ -685,7 +683,7 @@ def download( # TODO(v0.10): remove this function -def get_model_output(repo_id: str, data: Any, token: Optional[str] = None) -> Any: +def get_model_output(repo_id: str, data: Any, token: str | None = None) -> Any: """Returns the output of the model using Hugging Face Hub's inference API. See the :ref:`User Guide ` for more details. diff --git a/skops/io/_audit.py b/skops/io/_audit.py index 8d1352a1..423a4c7d 100644 --- a/skops/io/_audit.py +++ b/skops/io/_audit.py @@ -2,19 +2,19 @@ import io from contextlib import contextmanager -from typing import Any, Dict, Generator, List, Optional, Sequence, Type, Union +from typing import Any, Generator, Type, Union from ._protocol import PROTOCOL from ._utils import LoadContext, get_module, get_type_paths from .exceptions import UntrustedTypesFoundException NODE_TYPE_MAPPING: dict[tuple[str, int], Type[Node]] = {} -VALID_NODE_CHILD_TYPES = Optional[ - Union["Node", List["Node"], Dict[str, "Node"], Type, str, io.BytesIO] +VALID_NODE_CHILD_TYPES = Union[ + "Node", list["Node"], dict[str, "Node"], Type, str, io.BytesIO ] -def check_type(module_name: str, type_name: str, trusted: Sequence[str]) -> bool: +def check_type(module_name: str, type_name: str, trusted: list[str]) -> bool: """Check if a type is safe to load. A type is safe to load only if it's present in the trusted list. @@ -134,7 +134,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, memoize: bool = True, ) -> None: self.class_name, self.module_name = state["__class__"], state["__module__"] @@ -172,8 +172,8 @@ def _construct(self): @staticmethod def _get_trusted( - trusted: Optional[Sequence[Union[str, Type]]], - default: Sequence[Union[str, Type]], + trusted: list[str | Type] | None, + default: list[str | Type], ) -> list[str]: """Return a trusted list, or True. @@ -233,7 +233,7 @@ def get_unsafe_set(self) -> set[str]: continue # Get the safety set based on the type of the child. In most cases - # other than ListNode and DictNode, children are all of type Node. + # other than listNode and DictNode, children are all of type Node. if isinstance(child, list): # iterate through the list for value in child: @@ -278,7 +278,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[List[str]] = None, + trusted: list[str | type[Any]] | None = None, ): # we pass memoize as False because we don't want to memoize the cached # node. @@ -302,7 +302,7 @@ def _construct(self): def get_tree( state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]], + trusted: list[str | type[Any]] | None, ) -> Node: """Get the tree of nodes. diff --git a/skops/io/_general.py b/skops/io/_general.py index 46a3b1c8..564d60dc 100644 --- a/skops/io/_general.py +++ b/skops/io/_general.py @@ -7,7 +7,7 @@ from functools import partial from reprlib import Repr from types import FunctionType, MethodType -from typing import Any, Optional, Sequence +from typing import Any import numpy as np @@ -60,7 +60,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [dict]) @@ -97,7 +97,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [list]) @@ -129,7 +129,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [set]) @@ -161,7 +161,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [tuple]) @@ -208,7 +208,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) # TODO: what do we trust? @@ -252,7 +252,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) # TODO: should we trust anything? @@ -293,7 +293,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) # TODO: what do we trust? @@ -327,7 +327,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [slice]) @@ -390,7 +390,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) @@ -449,7 +449,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.children = { @@ -474,7 +474,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.content = state["content"] @@ -527,7 +527,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [bytes]) @@ -548,7 +548,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [bytearray]) @@ -578,7 +578,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, []) diff --git a/skops/io/_numpy.py b/skops/io/_numpy.py index f3887ba6..ef7d6f0d 100644 --- a/skops/io/_numpy.py +++ b/skops/io/_numpy.py @@ -1,7 +1,7 @@ from __future__ import annotations import io -from typing import Any, Optional, Sequence +from typing import Any import numpy as np @@ -61,7 +61,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.type = state["type"] @@ -128,7 +128,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = self._get_trusted(trusted, [np.ma.MaskedArray]) @@ -159,7 +159,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: super().__init__(state, load_context, trusted) # TODO @@ -190,7 +190,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.children = { @@ -231,7 +231,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.children = { diff --git a/skops/io/_persist.py b/skops/io/_persist.py index 711ec884..8babf27d 100644 --- a/skops/io/_persist.py +++ b/skops/io/_persist.py @@ -4,7 +4,7 @@ import io import json from pathlib import Path -from typing import Any, BinaryIO, Optional, Sequence +from typing import Any, BinaryIO from zipfile import ZIP_STORED, ZipFile import skops @@ -113,7 +113,7 @@ def dumps( return buffer.getbuffer().tobytes() -def load(file: str | Path, trusted: Optional[Sequence[str]] = None) -> Any: +def load(file: str | Path, trusted: list[str | type[Any]] | None = None) -> Any: """Load an object saved with the skops persistence format. Skops aims at providing a secure persistence feature that does not rely on @@ -154,7 +154,7 @@ def load(file: str | Path, trusted: Optional[Sequence[str]] = None) -> Any: return instance -def loads(data: bytes, trusted: Optional[Sequence[str]] = None) -> Any: +def loads(data: bytes, trusted: list[str | type[Any]] | None = None) -> Any: """Load an object saved with the skops persistence format from a bytes object. diff --git a/skops/io/_quantile_forest.py b/skops/io/_quantile_forest.py index dbe745a7..87d428d5 100644 --- a/skops/io/_quantile_forest.py +++ b/skops/io/_quantile_forest.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional, Sequence +from typing import Any from ._protocol import PROTOCOL from ._sklearn import ReduceNode, reduce_get_state @@ -26,7 +26,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: if QuantileForest is None: raise ImportError( diff --git a/skops/io/_scipy.py b/skops/io/_scipy.py index e482a1af..3c85494b 100644 --- a/skops/io/_scipy.py +++ b/skops/io/_scipy.py @@ -1,7 +1,7 @@ from __future__ import annotations import io -from typing import Any, Optional, Sequence +from typing import Any from scipy.sparse import load_npz, save_npz, spmatrix @@ -38,7 +38,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.type = state["type"] diff --git a/skops/io/_sklearn.py b/skops/io/_sklearn.py index 9f6058a1..a74afde5 100644 --- a/skops/io/_sklearn.py +++ b/skops/io/_sklearn.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional, Sequence, Type +from typing import Any, Type from sklearn.cluster import Birch @@ -98,7 +98,7 @@ def __init__( state: dict[str, Any], load_context: LoadContext, constructor: Type[Any], - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: super().__init__(state, load_context, trusted) reduce = state["__reduce__"] @@ -157,7 +157,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: self.trusted = self._get_trusted(trusted, [get_module(Tree) + ".Tree"]) super().__init__(state, load_context, constructor=Tree, trusted=self.trusted) @@ -174,7 +174,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: # TODO: make sure trusted here makes sense and used. self.trusted = self._get_trusted( @@ -215,7 +215,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.trusted = [ diff --git a/skops/io/_utils.py b/skops/io/_utils.py index d929380a..82805026 100644 --- a/skops/io/_utils.py +++ b/skops/io/_utils.py @@ -203,7 +203,7 @@ def get_type_paths(types: Any) -> list[str]: return [get_type_name(t) if not isinstance(t, str) else t for t in types] -def get_public_type_names(module: ModuleType, oftype: Type) -> list[str]: +def get_public_type_names(module: ModuleType, oftype: Type) -> list[str | type[Any]]: """ Helper function that gets the type names of all public objects of the given ``oftype`` from the given ``module``, @@ -233,4 +233,6 @@ def get_public_type_names(module: ModuleType, oftype: Type) -> list[str]: if issubclass((obj := getattr(module, attr)).__class__, oftype) and (type_name := get_type_name(obj)).startswith(module_name) } - ) + ) # type: ignore + # type check is ignored because list[str] can't be converted to + # list[str | type[Any]] diff --git a/skops/io/_visualize.py b/skops/io/_visualize.py index 7c4965d3..3a9e24ff 100644 --- a/skops/io/_visualize.py +++ b/skops/io/_visualize.py @@ -4,7 +4,7 @@ import json from dataclasses import dataclass from pathlib import Path -from typing import Any, Callable, Iterator, Literal, Optional, Sequence +from typing import Any, Callable, Iterator, Literal from zipfile import ZipFile from ._audit import VALID_NODE_CHILD_TYPES, Node, get_tree @@ -290,7 +290,7 @@ def visualize( file: Path | str | bytes, *, show: Literal["all", "untrusted", "trusted"] = "all", - trusted: Optional[Sequence[str]] = None, + trusted: [str] | None = None, sink: Callable[..., None] = pretty_print_tree, **kwargs: Any, ) -> None: diff --git a/skops/io/old/_general_v0.py b/skops/io/old/_general_v0.py index 4078227c..7408a4a4 100644 --- a/skops/io/old/_general_v0.py +++ b/skops/io/old/_general_v0.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional, Sequence +from typing import Any from skops.io._audit import Node from skops.io._trusted_types import SCIPY_UFUNC_TYPE_NAMES @@ -14,7 +14,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) # TODO: what do we trust? diff --git a/skops/io/old/_numpy_v0.py b/skops/io/old/_numpy_v0.py index 356f67d2..48e981b4 100644 --- a/skops/io/old/_numpy_v0.py +++ b/skops/io/old/_numpy_v0.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional, Sequence +from typing import Any import numpy as np @@ -15,7 +15,7 @@ def __init__( self, state: dict[str, Any], load_context: LoadContext, - trusted: Optional[Sequence[str]] = None, + trusted: list[str | type[Any]] | None = None, ) -> None: super().__init__(state, load_context, trusted) self.children = {"bit_generator_state": state["content"]["bit_generator"]}