Repository: huggingface/huggingface_hub
Pull request #651 (2022-02-04 10:56:41)

TensorBoard for Keras models
TensorBoard FTW


merveenoyan: @osanseviero I did resolved everything

Patch:
diff --git a/docs/hub/adding-a-model.md b/docs/hub/adding-a-model.md
index 49240cbd..c10fc246 100644
--- a/docs/hub/adding-a-model.md
+++ b/docs/hub/adding-a-model.md
@@ -46,7 +46,7 @@ If you look at the “Files and versions” tab, you’ll see that there aren’
 
 If you've used Git before, this will be very easy since Git is used to manage files in the Hub.
 
-There is only one key difference if you have large files (over 10MB). These files are usually tracked with **git-lfs** (which stands for Git Large File Storage). 
+There is only one key difference if you have large files (over 10MB, or 1MB for binary files). These files are usually tracked with **git-lfs** (which stands for Git Large File Storage). 
 
 1. Please make sure you have both **git** and **git-lfs** installed on your system.
 
diff --git a/js/src/lib/components/InferenceWidget/shared/WidgetTextarea/WidgetTextarea.svelte b/js/src/lib/components/InferenceWidget/shared/WidgetTextarea/WidgetTextarea.svelte
index 202786ce..a5b61ed8 100644
--- a/js/src/lib/components/InferenceWidget/shared/WidgetTextarea/WidgetTextarea.svelte
+++ b/js/src/lib/components/InferenceWidget/shared/WidgetTextarea/WidgetTextarea.svelte
@@ -1,43 +1,40 @@
 <script>
+	import { tick } from "svelte";
+
 	import WidgetLabel from "../WidgetLabel/WidgetLabel.svelte";
 
 	export let label: string = "";
 	export let placeholder: string = "Your sentence here...";
 	export let value: string;
 
-	// hack to handle FireFox contenteditable bug
-	let innterHTML: string;
-	let spanEl: HTMLSpanElement;
-	const REGEX_SPAN = /<span .+>(.*)<\/span>/gms;
-	$: {
-		if (spanEl && innterHTML && REGEX_SPAN.test(innterHTML)) {
-			innterHTML = innterHTML.replace(REGEX_SPAN, (_, txt) => {
-				return txt;
-			});
-			spanEl.blur();
+	let textAreaEl: HTMLTextAreaElement;
+
+	const HEIGHT_LIMIT = 500 as const;
+
+	async function resize() {
+		if (!!textAreaEl) {
+			textAreaEl.style.height = "0px";
+			await tick();
+			textAreaEl.style.height =
+				Math.min(textAreaEl.scrollHeight, HEIGHT_LIMIT) + "px";
 		}
 	}
+
+	$: {
+		value;
+		resize();
+	}
 </script>
 
 <WidgetLabel {label}>
 	<svelte:fragment slot="after">
-		<span
-			bind:textContent={value}
-			bind:innerHTML={innterHTML}
-			bind:this={spanEl}
+		<textarea
+			bind:this={textAreaEl}
+			bind:value
 			class="{label
 				? 'mt-1.5'
-				: ''} block overflow-auto resize-y py-2 px-3 w-full min-h-[42px] max-h-[500px] border border-gray-200 rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner dark:bg-gray-925"
-			role="textbox"
-			contenteditable
-			style="--placeholder: '{placeholder}'"
+				: ''} block w-full border border-gray-200 rounded-lg shadow-inner outline-none focus:ring-1 focus:ring-inset focus:ring-indigo-200 focus:shadow-inner dark:bg-gray-925"
+			{placeholder}
 		/>
 	</svelte:fragment>
 </WidgetLabel>
-
-<style>
-	span[contenteditable]:empty::before {
-		content: var(--placeholder);
-		color: rgba(156, 163, 175);
-	}
-</style>
diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py
index fc11433b..9e44b7de 100644
--- a/src/huggingface_hub/hf_api.py
+++ b/src/huggingface_hub/hf_api.py
@@ -517,6 +517,7 @@ class HfApi:
         direction: Optional[Literal[-1]] = None,
         limit: Optional[int] = None,
         full: Optional[bool] = None,
+        cardData: Optional[bool] = None,
         fetch_config: Optional[bool] = None,
         use_auth_token: Optional[Union[bool, str]] = None,
     ) -> List[ModelInfo]:
@@ -591,6 +592,9 @@ class HfApi:
             full (:obj:`bool`, `optional`):
                 Whether to fetch all model data, including the `lastModified`, the `sha`, the files and the `tags`.
                 This is set to `True` by default when using a filter.
+            cardData (:obj:`bool`, `optional`):
+                Whether to grab the metadata for the model as well. Can contain useful information such as carbon emissions,
+                metrics, and datasets trained on.
             fetch_config (:obj:`bool`, `optional`):
                 Whether to fetch the model configs as well. This is not included in `full` due to its size.
             use_auth_token (:obj:`bool` or :obj:`str`, `optional`):
@@ -635,7 +639,9 @@ class HfApi:
                 del params["full"]
         if fetch_config is not None:
             params.update({"config": fetch_config})
-        r = requests.get(path, headers=headers, params=params)
+        if cardData is not None:
+            params.update({"cardData": cardData})
+        r = requests.get(path, params=params, headers=headers)
         r.raise_for_status()
         d = r.json()
         return [ModelInfo(**x) for x in d]
@@ -708,6 +714,7 @@ class HfApi:
         sort: Union[Literal["lastModified"], str, None] = None,
         direction: Optional[Literal[-1]] = None,
         limit: Optional[int] = None,
+        cardData: Optional[bool] = None,
         full: Optional[bool] = None,
         use_auth_token: Optional[str] = None,
     ) -> List[DatasetInfo]:
@@ -779,6 +786,8 @@ class HfApi:
                 sort by ascending order.
             limit (:obj:`int`, `optional`):
                 The limit on the number of datasets fetched. Leaving this option to `None` fetches all datasets.
+            cardData (:obj:`bool`, `optional`):
+                Whether to grab the metadata for the dataset as well. Can contain useful information such as the PapersWithCode ID.
             full (:obj:`bool`, `optional`):
                 Whether to fetch all dataset data, including the `lastModified` and the `cardData`.
             use_auth_token (:obj:`bool` or :obj:`str`, `optional`):
@@ -818,7 +827,10 @@ class HfApi:
         if full is not None:
             if full:
                 params.update({"full": True})
-        r = requests.get(path, headers=headers, params=params)
+        if cardData is not None:
+            if cardData:
+                params.update({"full": True})
+        r = requests.get(path, params=params, headers=headers)
         r.raise_for_status()
         d = r.json()
         return [DatasetInfo(**x) for x in d]
diff --git a/src/huggingface_hub/keras_mixin.py b/src/huggingface_hub/keras_mixin.py
index 6bea285e..a9ecb104 100644
--- a/src/huggingface_hub/keras_mixin.py
+++ b/src/huggingface_hub/keras_mixin.py
@@ -2,6 +2,7 @@ import json
 import logging
 import os
 from pathlib import Path
+from shutil import copytree
 from typing import Any, Dict, Optional, Union
 
 from huggingface_hub import ModelHubMixin
@@ -71,6 +72,7 @@ def push_to_hub_keras(
     model,
     repo_path_or_name: Optional[str] = None,
     repo_url: Optional[str] = None,
+    log_dir: Optional[str] = None,
     commit_message: Optional[str] = "Add model",
     organization: Optional[str] = None,
     private: Optional[bool] = None,
@@ -97,6 +99,9 @@ def push_to_hub_keras(
             Specify this in case you want to push to an existing repository in the hub. If unspecified, a new
             repository will be created in your namespace (unless you specify an :obj:`organization`) with
             :obj:`repo_name`.
+        log_dir (:obj:`str`, `optional`):
+            TensorBoard logging directory to be pushed. The Hub automatically hosts
+            and displays a TensorBoard instance if log files are included in the repository.
         commit_message (:obj:`str`, `optional`):
             Message to commit while pushing. Will default to :obj:`"add model"`.
         organization (:obj:`str`, `optional`):
@@ -173,7 +178,8 @@ def push_to_hub_keras(
         include_optimizer=include_optimizer,
         **model_save_kwargs,
     )
-
+    if log_dir is not None:
+        copytree(log_dir, f"{repo_path_or_name}/logs")
     # Commit and push!
     repo.git_add(auto_lfs_track=True)
     repo.git_commit(commit_message)


Test patch:
diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py
index a4bbec42..b7ab619b 100644
--- a/tests/test_hf_api.py
+++ b/tests/test_hf_api.py
@@ -672,6 +672,21 @@ class HfApiPublicTest(unittest.TestCase):
         self.assertGreater(len(datasets), 10)
         self.assertIsInstance(datasets[0], DatasetInfo)
 
+    @with_production_testing
+    def test_filter_datasets_with_cardData(self):
+        _api = HfApi()
+        datasets = _api.list_datasets(cardData=True)
+        self.assertGreater(
+            sum(
+                [getattr(dataset, "cardData", None) is not None for dataset in datasets]
+            ),
+            0,
+        )
+        datasets = _api.list_datasets()
+        self.assertTrue(
+            all([getattr(dataset, "cardData", None) is None for dataset in datasets])
+        )
+
     @with_production_testing
     def test_dataset_info(self):
         _api = HfApi()
@@ -780,6 +795,14 @@ class HfApiPublicTest(unittest.TestCase):
             ["pytorch" in model.tags and "tf" in model.tags for model in models]
         )
 
+    @with_production_testing
+    def test_filter_models_with_cardData(self):
+        _api = HfApi()
+        models = _api.list_models("co2_eq_emissions", cardData=True)
+        self.assertTrue([hasattr(model, "cardData") for model in models])
+        models = _api.list_models("co2_eq_emissions")
+        self.assertTrue(all([not hasattr(model, "cardData") for model in models]))
+
 
 class HfApiPrivateTest(HfApiCommonTestWithLogin):
     def setUp(self) -> None:
diff --git a/tests/test_keras_integration.py b/tests/test_keras_integration.py
index d5ccbbe3..1b887ee2 100644
--- a/tests/test_keras_integration.py
+++ b/tests/test_keras_integration.py
@@ -292,6 +292,32 @@ class HubKerasSequentialTest(HubMixingTestKeras):
 
         self._api.delete_repo(name=f"{REPO_NAME}", token=self._token)
 
+    def test_push_to_hub_tensorboard(self):
+        os.makedirs(f"{WORKING_REPO_DIR}/tb_log_dir")
+        with open(f"{WORKING_REPO_DIR}/tb_log_dir/tensorboard.txt", "w") as fp:
+            fp.write("Keras FTW")
+        REPO_NAME = repo_name("PUSH_TO_HUB")
+        model = self.model_init()
+        model.build((None, 2))
+        push_to_hub_keras(
+            model,
+            repo_path_or_name=f"{WORKING_REPO_DIR}/{REPO_NAME}",
+            log_dir=f"{WORKING_REPO_DIR}/tb_log_dir",
+            api_endpoint=ENDPOINT_STAGING,
+            use_auth_token=self._token,
+            git_user="ci",
+            git_email="ci@dummy.com",
+        )
+        model_info = HfApi(endpoint=ENDPOINT_STAGING).model_info(
+            f"{USER}/{REPO_NAME}",
+        )
+
+        self.assertTrue(
+            "logs/tensorboard.txt" in [f.rfilename for f in model_info.siblings]
+        )
+
+        self._api.delete_repo(name=f"{REPO_NAME}", token=self._token)
+
     def test_push_to_hub_model_kwargs(self):
         REPO_NAME = repo_name("PUSH_TO_HUB")
         model = self.model_init()