Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
repocard.py827 linesDownload Raw Back to huggingface_hub
1import os2import re3from pathlib import Path4from typing import Any, Literal5 6import yaml7 8from huggingface_hub.file_download import hf_hub_download9from huggingface_hub.hf_api import upload_file10from huggingface_hub.repocard_data import (11    CardData,12    DatasetCardData,13    EvalResult,14    ModelCardData,15    SpaceCardData,16    eval_results_to_model_index,17    model_index_to_eval_results,18)19from huggingface_hub.utils import HfHubHTTPError, get_session, hf_raise_for_status, is_jinja_available, yaml_dump20 21from . import constants22from .errors import EntryNotFoundError23from .utils import SoftTemporaryDirectory, logging, validate_hf_hub_args24 25 26logger = logging.get_logger(__name__)27 28 29TEMPLATE_MODELCARD_PATH = Path(__file__).parent / "templates" / "modelcard_template.md"30TEMPLATE_DATASETCARD_PATH = Path(__file__).parent / "templates" / "datasetcard_template.md"31 32# exact same regex as in the Hub server. Please keep in sync.33# See https://github.com/huggingface/moon-landing/blob/main/server/lib/ViewMarkdown.ts#L1834REGEX_YAML_BLOCK = re.compile(r"^(\s*---[\r\n]+)([\S\s]*?)([\r\n]+---(\r\n|\n|$))")35 36 37class RepoCard:38    card_data_class = CardData39    default_template_path = TEMPLATE_MODELCARD_PATH40    repo_type = "model"41 42    def __init__(self, content: str, ignore_metadata_errors: bool = False):43        """Initialize a RepoCard from string content. The content should be a44        Markdown file with a YAML block at the beginning and a Markdown body.45 46        Args:47            content (`str`): The content of the Markdown file.48 49        Example:50            ```python51            >>> from huggingface_hub.repocard import RepoCard52            >>> text = '''53            ... ---54            ... language: en55            ... license: mit56            ... ---57            ...58            ... # My repo59            ... '''60            >>> card = RepoCard(text)61            >>> card.data.to_dict()62            {'language': 'en', 'license': 'mit'}63            >>> card.text64            '\\n# My repo\\n'65 66            ```67        > [!TIP]68        > Raises the following error:69        >70        >     - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)71        >       when the content of the repo card metadata is not a dictionary.72        """73 74        # Set the content of the RepoCard, as well as underlying .data and .text attributes.75        # See the `content` property setter for more details.76        self.ignore_metadata_errors = ignore_metadata_errors77        self.content = content78 79    @property80    def content(self):81        """The content of the RepoCard, including the YAML block and the Markdown body."""82        line_break = _detect_line_ending(self._content) or "\n"83        return f"---{line_break}{self.data.to_yaml(line_break=line_break, original_order=self._original_order)}{line_break}---{line_break}{self.text}"84 85    @content.setter86    def content(self, content: str):87        """Set the content of the RepoCard."""88        self._content = content89 90        match = REGEX_YAML_BLOCK.search(content)91        if match:92            # Metadata found in the YAML block93            yaml_block = match.group(2)94            self.text = content[match.end() :]95            data_dict = yaml.safe_load(yaml_block)96 97            if data_dict is None:98                data_dict = {}99 100            # The YAML block's data should be a dictionary101            if not isinstance(data_dict, dict):102                raise ValueError("repo card metadata block should be a dict")103        else:104            # Model card without metadata... create empty metadata105            logger.warning("Repo card metadata block was not found. Setting CardData to empty.")106            data_dict = {}107            self.text = content108 109        self.data = self.card_data_class(**data_dict, ignore_metadata_errors=self.ignore_metadata_errors)110        self._original_order = list(data_dict.keys())111 112    def __str__(self):113        return self.content114 115    def save(self, filepath: Path | str):116        r"""Save a RepoCard to a file.117 118        Args:119            filepath (`Union[Path, str]`): Filepath to the markdown file to save.120 121        Example:122            ```python123            >>> from huggingface_hub.repocard import RepoCard124            >>> card = RepoCard("---\nlanguage: en\n---\n# This is a test repo card")125            >>> card.save("/tmp/test.md")126 127            ```128        """129        filepath = Path(filepath)130        filepath.parent.mkdir(parents=True, exist_ok=True)131        # Preserve newlines as in the existing file.132        with open(filepath, mode="w", newline="", encoding="utf-8") as f:133            f.write(str(self))134 135    @classmethod136    def load(137        cls,138        repo_id_or_path: str | Path,139        repo_type: str | None = None,140        token: str | None = None,141        ignore_metadata_errors: bool = False,142    ):143        """Initialize a RepoCard from a Hugging Face Hub repo's README.md or a local filepath.144 145        Args:146            repo_id_or_path (`Union[str, Path]`):147                The repo ID associated with a Hugging Face Hub repo or a local filepath.148            repo_type (`str`, *optional*):149                The type of Hugging Face repo to push to. Defaults to None, which will use "model". Other options150                are "dataset" and "space". Not used when loading from a local filepath. If this is called from a child151                class, the default value will be the child class's `repo_type`.152            token (`str`, *optional*):153                Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to the stored token.154            ignore_metadata_errors (`str`):155                If True, errors while parsing the metadata section will be ignored. Some information might be lost during156                the process. Use it at your own risk.157 158        Returns:159            [`huggingface_hub.repocard.RepoCard`]: The RepoCard (or subclass) initialized from the repo's160                README.md file or filepath.161 162        Example:163            ```python164            >>> from huggingface_hub.repocard import RepoCard165            >>> card = RepoCard.load("nateraw/food")166            >>> assert card.data.tags == ["generated_from_trainer", "image-classification", "pytorch"]167 168            ```169        """170 171        if Path(repo_id_or_path).is_file():172            card_path = Path(repo_id_or_path)173        elif isinstance(repo_id_or_path, str):174            card_path = Path(175                hf_hub_download(176                    repo_id_or_path,177                    constants.REPOCARD_NAME,178                    repo_type=repo_type or cls.repo_type,179                    token=token,180                )181            )182        else:183            raise ValueError(f"Cannot load RepoCard: path not found on disk ({repo_id_or_path}).")184 185        # Preserve newlines in the existing file.186        with card_path.open(mode="r", newline="", encoding="utf-8") as f:187            return cls(f.read(), ignore_metadata_errors=ignore_metadata_errors)188 189    def validate(self, repo_type: str | None = None):190        """Validates card against Hugging Face Hub's card validation logic.191        Using this function requires access to the internet, so it is only called192        internally by [`huggingface_hub.repocard.RepoCard.push_to_hub`].193 194        Args:195            repo_type (`str`, *optional*, defaults to "model"):196                The type of Hugging Face repo to push to. Options are "model", "dataset", and "space".197                If this function is called from a child class, the default will be the child class's `repo_type`.198 199        > [!TIP]200        > Raises the following errors:201        >202        >     - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)203        >       if the card fails validation checks.204        >     - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)205        >       if the request to the Hub API fails for any other reason.206        """207 208        # If repo type is provided, otherwise, use the repo type of the card.209        repo_type = repo_type or self.repo_type210 211        body = {212            "repoType": repo_type,213            "content": str(self),214        }215        headers = {"Accept": "text/plain"}216 217        try:218            response = get_session().post("https://huggingface.co/api/validate-yaml", json=body, headers=headers)219            hf_raise_for_status(response)220        except HfHubHTTPError as exc:221            if response.status_code == 400:222                raise ValueError(response.text)223            else:224                raise exc225 226    def push_to_hub(227        self,228        repo_id: str,229        token: str | None = None,230        repo_type: str | None = None,231        commit_message: str | None = None,232        commit_description: str | None = None,233        revision: str | None = None,234        create_pr: bool | None = None,235        parent_commit: str | None = None,236    ):237        """Push a RepoCard to a Hugging Face Hub repo.238 239        Args:240            repo_id (`str`):241                The repo ID of the Hugging Face Hub repo to push to. Example: "nateraw/food".242            token (`str`, *optional*):243                Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to244                the stored token.245            repo_type (`str`, *optional*, defaults to "model"):246                The type of Hugging Face repo to push to. Options are "model", "dataset", and "space". If this247                function is called by a child class, it will default to the child class's `repo_type`.248            commit_message (`str`, *optional*):249                The summary / title / first line of the generated commit.250            commit_description (`str`, *optional*)251                The description of the generated commit.252            revision (`str`, *optional*):253                The git revision to commit from. Defaults to the head of the `"main"` branch.254            create_pr (`bool`, *optional*):255                Whether or not to create a Pull Request with this commit. Defaults to `False`.256            parent_commit (`str`, *optional*):257                The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.258                If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.259                If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.260                Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be261                especially useful if the repo is updated / committed too concurrently.262        Returns:263            `str`: URL of the commit which updated the card metadata.264        """265 266        # If repo type is provided, otherwise, use the repo type of the card.267        repo_type = repo_type or self.repo_type268 269        # Validate card before pushing to hub270        self.validate(repo_type=repo_type)271 272        with SoftTemporaryDirectory() as tmpdir:273            tmp_path = Path(tmpdir) / constants.REPOCARD_NAME274            tmp_path.write_text(str(self), encoding="utf-8")275            url = upload_file(276                path_or_fileobj=str(tmp_path),277                path_in_repo=constants.REPOCARD_NAME,278                repo_id=repo_id,279                token=token,280                repo_type=repo_type,281                commit_message=commit_message,282                commit_description=commit_description,283                create_pr=create_pr,284                revision=revision,285                parent_commit=parent_commit,286            )287        return url288 289    @classmethod290    def from_template(291        cls,292        card_data: CardData,293        template_path: str | None = None,294        template_str: str | None = None,295        **template_kwargs,296    ):297        """Initialize a RepoCard from a template. By default, it uses the default template.298 299        Templates are Jinja2 templates that can be customized by passing keyword arguments.300 301        Args:302            card_data (`huggingface_hub.CardData`):303                A huggingface_hub.CardData instance containing the metadata you want to include in the YAML304                header of the repo card on the Hugging Face Hub.305            template_path (`str`, *optional*):306                A path to a markdown file with optional Jinja template variables that can be filled307                in with `template_kwargs`. Defaults to the default template.308 309        Returns:310            [`huggingface_hub.repocard.RepoCard`]: A RepoCard instance with the specified card data and content from the311            template.312        """313        if is_jinja_available():314            import jinja2315        else:316            raise ImportError(317                "Using RepoCard.from_template requires Jinja2 to be installed. Please"318                " install it with `pip install Jinja2`."319            )320 321        kwargs = card_data.to_dict().copy()322        kwargs.update(template_kwargs)  # Template_kwargs have priority323 324        if template_path is not None:325            template_str = Path(template_path).read_text()326        if template_str is None:327            template_str = Path(cls.default_template_path).read_text()328        template = jinja2.Template(template_str)329        content = template.render(card_data=card_data.to_yaml(), **kwargs)330        return cls(content)331 332 333class ModelCard(RepoCard):334    card_data_class = ModelCardData  # type: ignore[assignment]335    default_template_path = TEMPLATE_MODELCARD_PATH336    repo_type = "model"337 338    @classmethod339    def from_template(  # type: ignore # violates Liskov property but easier to use340        cls,341        card_data: ModelCardData,342        template_path: str | None = None,343        template_str: str | None = None,344        **template_kwargs,345    ):346        """Initialize a ModelCard from a template. By default, it uses the default template, which can be found here:347        https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/modelcard_template.md348 349        Templates are Jinja2 templates that can be customized by passing keyword arguments.350 351        Args:352            card_data (`huggingface_hub.ModelCardData`):353                A huggingface_hub.ModelCardData instance containing the metadata you want to include in the YAML354                header of the model card on the Hugging Face Hub.355            template_path (`str`, *optional*):356                A path to a markdown file with optional Jinja template variables that can be filled357                in with `template_kwargs`. Defaults to the default template.358 359        Returns:360            [`huggingface_hub.ModelCard`]: A ModelCard instance with the specified card data and content from the361            template.362 363        Example:364            ```python365            >>> from huggingface_hub import ModelCard, ModelCardData, EvalResult366 367            >>> # Using the Default Template368            >>> card_data = ModelCardData(369            ...     language='en',370            ...     license='mit',371            ...     library_name='timm',372            ...     tags=['image-classification', 'resnet'],373            ...     datasets=['beans'],374            ...     metrics=['accuracy'],375            ... )376            >>> card = ModelCard.from_template(377            ...     card_data,378            ...     model_description='This model does x + y...'379            ... )380 381            >>> # Including Evaluation Results382            >>> card_data = ModelCardData(383            ...     language='en',384            ...     tags=['image-classification', 'resnet'],385            ...     eval_results=[386            ...         EvalResult(387            ...             task_type='image-classification',388            ...             dataset_type='beans',389            ...             dataset_name='Beans',390            ...             metric_type='accuracy',391            ...             metric_value=0.9,392            ...         ),393            ...     ],394            ...     model_name='my-cool-model',395            ... )396            >>> card = ModelCard.from_template(card_data)397 398            >>> # Using a Custom Template399            >>> card_data = ModelCardData(400            ...     language='en',401            ...     tags=['image-classification', 'resnet']402            ... )403            >>> card = ModelCard.from_template(404            ...     card_data=card_data,405            ...     template_path='./src/huggingface_hub/templates/modelcard_template.md',406            ...     custom_template_var='custom value',  # will be replaced in template if it exists407            ... )408 409            ```410        """411        return super().from_template(card_data, template_path, template_str, **template_kwargs)412 413 414class DatasetCard(RepoCard):415    card_data_class = DatasetCardData  # type: ignore[assignment]416    default_template_path = TEMPLATE_DATASETCARD_PATH417    repo_type = "dataset"418 419    @classmethod420    def from_template(  # type: ignore # violates Liskov property but easier to use421        cls,422        card_data: DatasetCardData,423        template_path: str | None = None,424        template_str: str | None = None,425        **template_kwargs,426    ):427        """Initialize a DatasetCard from a template. By default, it uses the default template, which can be found here:428        https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/datasetcard_template.md429 430        Templates are Jinja2 templates that can be customized by passing keyword arguments.431 432        Args:433            card_data (`huggingface_hub.DatasetCardData`):434                A huggingface_hub.DatasetCardData instance containing the metadata you want to include in the YAML435                header of the dataset card on the Hugging Face Hub.436            template_path (`str`, *optional*):437                A path to a markdown file with optional Jinja template variables that can be filled438                in with `template_kwargs`. Defaults to the default template.439 440        Returns:441            [`huggingface_hub.DatasetCard`]: A DatasetCard instance with the specified card data and content from the442            template.443 444        Example:445            ```python446            >>> from huggingface_hub import DatasetCard, DatasetCardData447 448            >>> # Using the Default Template449            >>> card_data = DatasetCardData(450            ...     language='en',451            ...     license='mit',452            ...     annotations_creators='crowdsourced',453            ...     task_categories=['text-classification'],454            ...     task_ids=['sentiment-classification', 'text-scoring'],455            ...     multilinguality='monolingual',456            ...     pretty_name='My Text Classification Dataset',457            ... )458            >>> card = DatasetCard.from_template(459            ...     card_data,460            ...     pretty_name=card_data.pretty_name,461            ... )462 463            >>> # Using a Custom Template464            >>> card_data = DatasetCardData(465            ...     language='en',466            ...     license='mit',467            ... )468            >>> card = DatasetCard.from_template(469            ...     card_data=card_data,470            ...     template_path='./src/huggingface_hub/templates/datasetcard_template.md',471            ...     custom_template_var='custom value',  # will be replaced in template if it exists472            ... )473 474            ```475        """476        return super().from_template(card_data, template_path, template_str, **template_kwargs)477 478 479class SpaceCard(RepoCard):480    card_data_class = SpaceCardData  # type: ignore[assignment]481    default_template_path = TEMPLATE_MODELCARD_PATH482    repo_type = "space"483 484 485def _detect_line_ending(content: str) -> Literal["\r", "\n", "\r\n", None]:  # noqa: F722486    """Detect the line ending of a string. Used by RepoCard to avoid making huge diff on newlines.487 488    Uses same implementation as in Hub server, keep it in sync.489 490    Returns:491        str: The detected line ending of the string.492    """493    cr = content.count("\r")494    lf = content.count("\n")495    crlf = content.count("\r\n")496    if cr + lf == 0:497        return None498    if crlf == cr and crlf == lf:499        return "\r\n"500    if cr > lf:501        return "\r"502    else:503        return "\n"504 505 506def metadata_load(local_path: str | Path) -> dict | None:507    content = Path(local_path).read_text()508    match = REGEX_YAML_BLOCK.search(content)509    if match:510        yaml_block = match.group(2)511        data = yaml.safe_load(yaml_block)512        if data is None or isinstance(data, dict):513            return data514        raise ValueError("repo card metadata block should be a dict")515    else:516        return None517 518 519def metadata_save(local_path: str | Path, data: dict) -> None:520    """521    Save the metadata dict in the upper YAML part Trying to preserve newlines as522    in the existing file. Docs about open() with newline="" parameter:523    https://docs.python.org/3/library/functions.html?highlight=open#open Does524    not work with "^M" linebreaks, which are replaced by \n525    """526    line_break = "\n"527    content = ""528    # try to detect existing newline character529    if os.path.exists(local_path):530        with open(local_path, newline="", encoding="utf8") as readme:531            content = readme.read()532            if isinstance(readme.newlines, tuple):533                line_break = readme.newlines[0]534            elif isinstance(readme.newlines, str):535                line_break = readme.newlines536 537    # creates a new file if it not538    with open(local_path, "w", newline="", encoding="utf8") as readme:539        data_yaml = yaml_dump(data, sort_keys=False, line_break=line_break)540        # sort_keys: keep dict order541        match = REGEX_YAML_BLOCK.search(content)542        if match:543            output = content[: match.start()] + f"---{line_break}{data_yaml}---{line_break}" + content[match.end() :]544        else:545            output = f"---{line_break}{data_yaml}---{line_break}{content}"546 547        readme.write(output)548        readme.close()549 550 551def metadata_eval_result(552    *,553    model_pretty_name: str,554    task_pretty_name: str,555    task_id: str,556    metrics_pretty_name: str,557    metrics_id: str,558    metrics_value: Any,559    dataset_pretty_name: str,560    dataset_id: str,561    metrics_config: str | None = None,562    metrics_verified: bool = False,563    dataset_config: str | None = None,564    dataset_split: str | None = None,565    dataset_revision: str | None = None,566    metrics_verification_token: str | None = None,567) -> dict:568    """569    Creates a metadata dict with the result from a model evaluated on a dataset.570 571    Args:572        model_pretty_name (`str`):573            The name of the model in natural language.574        task_pretty_name (`str`):575            The name of a task in natural language.576        task_id (`str`):577            Example: automatic-speech-recognition. A task id.578        metrics_pretty_name (`str`):579            A name for the metric in natural language. Example: Test WER.580        metrics_id (`str`):581            Example: wer. A metric id from https://hf.co/metrics.582        metrics_value (`Any`):583            The value from the metric. Example: 20.0 or "20.0 ± 1.2".584        dataset_pretty_name (`str`):585            The name of the dataset in natural language.586        dataset_id (`str`):587            Example: common_voice. A dataset id from https://hf.co/datasets.588        metrics_config (`str`, *optional*):589            The name of the metric configuration used in `load_metric()`.590            Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.591        metrics_verified (`bool`, *optional*, defaults to `False`):592            Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.593        dataset_config (`str`, *optional*):594            Example: fr. The name of the dataset configuration used in `load_dataset()`.595        dataset_split (`str`, *optional*):596            Example: test. The name of the dataset split used in `load_dataset()`.597        dataset_revision (`str`, *optional*):598            Example: 5503434ddd753f426f4b38109466949a1217c2bb. The name of the dataset dataset revision599            used in `load_dataset()`.600        metrics_verification_token (`bool`, *optional*):601            A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.602 603    Returns:604        `dict`: a metadata dict with the result from a model evaluated on a dataset.605 606    Example:607        ```python608        >>> from huggingface_hub import metadata_eval_result609        >>> results = metadata_eval_result(610        ...         model_pretty_name="RoBERTa fine-tuned on ReactionGIF",611        ...         task_pretty_name="Text Classification",612        ...         task_id="text-classification",613        ...         metrics_pretty_name="Accuracy",614        ...         metrics_id="accuracy",615        ...         metrics_value=0.2662102282047272,616        ...         dataset_pretty_name="ReactionJPEG",617        ...         dataset_id="julien-c/reactionjpeg",618        ...         dataset_config="default",619        ...         dataset_split="test",620        ... )621        >>> results == {622        ...     'model-index': [623        ...         {624        ...             'name': 'RoBERTa fine-tuned on ReactionGIF',625        ...             'results': [626        ...                 {627        ...                     'task': {628        ...                         'type': 'text-classification',629        ...                         'name': 'Text Classification'630        ...                     },631        ...                     'dataset': {632        ...                         'name': 'ReactionJPEG',633        ...                         'type': 'julien-c/reactionjpeg',634        ...                         'config': 'default',635        ...                         'split': 'test'636        ...                     },637        ...                     'metrics': [638        ...                         {639        ...                             'type': 'accuracy',640        ...                             'value': 0.2662102282047272,641        ...                             'name': 'Accuracy',642        ...                             'verified': False643        ...                         }644        ...                     ]645        ...                 }646        ...             ]647        ...         }648        ...     ]649        ... }650        True651 652        ```653    """654 655    return {656        "model-index": eval_results_to_model_index(657            model_name=model_pretty_name,658            eval_results=[659                EvalResult(660                    task_name=task_pretty_name,661                    task_type=task_id,662                    metric_name=metrics_pretty_name,663                    metric_type=metrics_id,664                    metric_value=metrics_value,665                    dataset_name=dataset_pretty_name,666                    dataset_type=dataset_id,667                    metric_config=metrics_config,668                    verified=metrics_verified,669                    verify_token=metrics_verification_token,670                    dataset_config=dataset_config,671                    dataset_split=dataset_split,672                    dataset_revision=dataset_revision,673                )674            ],675        )676    }677 678 679@validate_hf_hub_args680def metadata_update(681    repo_id: str,682    metadata: dict,683    *,684    repo_type: str | None = None,685    overwrite: bool = False,686    token: str | None = None,687    commit_message: str | None = None,688    commit_description: str | None = None,689    revision: str | None = None,690    create_pr: bool = False,691    parent_commit: str | None = None,692) -> str:693    """694    Updates the metadata in the README.md of a repository on the Hugging Face Hub.695    If the README.md file doesn't exist yet, a new one is created with metadata and696    the default ModelCard or DatasetCard template. For `space` repo, an error is thrown697    as a Space cannot exist without a `README.md` file.698 699    Args:700        repo_id (`str`):701            The name of the repository.702        metadata (`dict`):703            A dictionary containing the metadata to be updated.704        repo_type (`str`, *optional*):705            Set to `"dataset"` or `"space"` if updating to a dataset or space,706            `None` or `"model"` if updating to a model. Default is `None`.707        overwrite (`bool`, *optional*, defaults to `False`):708            If set to `True` an existing field can be overwritten, otherwise709            attempting to overwrite an existing field will cause an error.710        token (`str`, *optional*):711            The Hugging Face authentication token.712        commit_message (`str`, *optional*):713            The summary / title / first line of the generated commit. Defaults to714            `f"Update metadata with huggingface_hub"`715        commit_description (`str` *optional*)716            The description of the generated commit717        revision (`str`, *optional*):718            The git revision to commit from. Defaults to the head of the719            `"main"` branch.720        create_pr (`boolean`, *optional*):721            Whether or not to create a Pull Request from `revision` with that commit.722            Defaults to `False`.723        parent_commit (`str`, *optional*):724            The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.725            If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.726            If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.727            Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be728            especially useful if the repo is updated / committed too concurrently.729    Returns:730        `str`: URL of the commit which updated the card metadata.731 732    Example:733        ```python734        >>> from huggingface_hub import metadata_update735        >>> metadata = {'model-index': [{'name': 'RoBERTa fine-tuned on ReactionGIF',736        ...             'results': [{'dataset': {'name': 'ReactionGIF',737        ...                                      'type': 'julien-c/reactiongif'},738        ...                           'metrics': [{'name': 'Recall',739        ...                                        'type': 'recall',740        ...                                        'value': 0.7762102282047272}],741        ...                          'task': {'name': 'Text Classification',742        ...                                   'type': 'text-classification'}}]}]}743        >>> url = metadata_update("hf-internal-testing/reactiongif-roberta-card", metadata)744 745        ```746    """747    commit_message = commit_message if commit_message is not None else "Update metadata with huggingface_hub"748 749    # Card class given repo_type750    card_class: type[RepoCard]751    if repo_type is None or repo_type == "model":752        card_class = ModelCard753    elif repo_type == "dataset":754        card_class = DatasetCard755    elif repo_type == "space":756        card_class = RepoCard757    else:758        raise ValueError(f"Unknown repo_type: {repo_type}")759 760    # Either load repo_card from the Hub or create an empty one.761    # NOTE: Will not create the repo if it doesn't exist.762    try:763        card = card_class.load(repo_id, token=token, repo_type=repo_type)764    except EntryNotFoundError:765        if repo_type == "space":766            raise ValueError("Cannot update metadata on a Space that doesn't contain a `README.md` file.")767 768        # Initialize a ModelCard or DatasetCard from default template and no data.769        # Cast to the concrete expected card type to satisfy type checkers.770        card = card_class.from_template(CardData())  # type: ignore771 772    for key, value in metadata.items():773        if key == "model-index":774            # if the new metadata doesn't include a name, either use existing one or repo name775            if "name" not in value[0]:776                value[0]["name"] = getattr(card, "model_name", repo_id)777            model_name, new_results = model_index_to_eval_results(value)778            if card.data.eval_results is None:779                card.data.eval_results = new_results780                card.data.model_name = model_name781            else:782                existing_results = card.data.eval_results783 784                # Iterate over new results785                #   Iterate over existing results786                #       If both results describe the same metric but value is different:787                #           If overwrite=True: overwrite the metric value788                #           Else: raise ValueError789                #       Else: append new result to existing ones.790                for new_result in new_results:791                    result_found = False792                    for existing_result in existing_results:793                        if new_result.is_equal_except_value(existing_result):794                            if new_result != existing_result and not overwrite:795                                raise ValueError(796                                    "You passed a new value for the existing metric"797                                    f" 'name: {new_result.metric_name}, type: "798                                    f"{new_result.metric_type}'. Set `overwrite=True`"799                                    " to overwrite existing metrics."800                                )801                            result_found = True802                            existing_result.metric_value = new_result.metric_value803                            if existing_result.verified is True:804                                existing_result.verify_token = new_result.verify_token805                    if not result_found:806                        card.data.eval_results.append(new_result)807        else:808            # Any metadata that is not a result metric809            if card.data.get(key) is not None and not overwrite and card.data.get(key) != value:810                raise ValueError(811                    f"You passed a new value for the existing meta data field '{key}'."812                    " Set `overwrite=True` to overwrite existing metadata."813                )814            else:815                card.data[key] = value816 817    return card.push_to_hub(818        repo_id,819        token=token,820        repo_type=repo_type,821        commit_message=commit_message,822        commit_description=commit_description,823        create_pr=create_pr,824        revision=revision,825        parent_commit=parent_commit,826    )827 
codekingpro/portable-devtools · Team Ai