1313# limitations under the License. 
1414"""Contains CLI utilities (styling, helpers).""" 
1515
16+ import  importlib .metadata 
1617import  os 
18+ import  time 
1719from  enum  import  Enum 
18- from  typing  import  TYPE_CHECKING , Annotated , Optional , Union 
20+ from  pathlib  import  Path 
21+ from  typing  import  TYPE_CHECKING , Annotated , Optional 
1922
2023import  click 
2124import  typer 
2225
23- from  huggingface_hub  import  __version__ 
26+ from  huggingface_hub  import  __version__ , constants 
27+ from  huggingface_hub .utils  import  ANSI , get_session , hf_raise_for_status , installation_method , logging 
28+ 
29+ 
30+ logger  =  logging .get_logger ()
2431
2532
2633if  TYPE_CHECKING :
@@ -34,58 +41,6 @@ def get_hf_api(token: Optional[str] = None) -> "HfApi":
3441    return  HfApi (token = token , library_name = "hf" , library_version = __version__ )
3542
3643
37- class  ANSI :
38-     """ 
39-     Helper for en.wikipedia.org/wiki/ANSI_escape_code 
40-     """ 
41- 
42-     _bold  =  "\u001b [1m" 
43-     _gray  =  "\u001b [90m" 
44-     _red  =  "\u001b [31m" 
45-     _reset  =  "\u001b [0m" 
46-     _yellow  =  "\u001b [33m" 
47- 
48-     @classmethod  
49-     def  bold (cls , s : str ) ->  str :
50-         return  cls ._format (s , cls ._bold )
51- 
52-     @classmethod  
53-     def  gray (cls , s : str ) ->  str :
54-         return  cls ._format (s , cls ._gray )
55- 
56-     @classmethod  
57-     def  red (cls , s : str ) ->  str :
58-         return  cls ._format (s , cls ._bold  +  cls ._red )
59- 
60-     @classmethod  
61-     def  yellow (cls , s : str ) ->  str :
62-         return  cls ._format (s , cls ._yellow )
63- 
64-     @classmethod  
65-     def  _format (cls , s : str , code : str ) ->  str :
66-         if  os .environ .get ("NO_COLOR" ):
67-             # See https://no-color.org/ 
68-             return  s 
69-         return  f"{ code } { s } { cls ._reset }  
70- 
71- 
72- def  tabulate (rows : list [list [Union [str , int ]]], headers : list [str ]) ->  str :
73-     """ 
74-     Inspired by: 
75- 
76-     - stackoverflow.com/a/8356620/593036 
77-     - stackoverflow.com/questions/9535954/printing-lists-as-tabular-data 
78-     """ 
79-     col_widths  =  [max (len (str (x )) for  x  in  col ) for  col  in  zip (* rows , headers )]
80-     row_format  =  ("{{:{}}} "  *  len (headers )).format (* col_widths )
81-     lines  =  []
82-     lines .append (row_format .format (* headers ))
83-     lines .append (row_format .format (* ["-"  *  w  for  w  in  col_widths ]))
84-     for  row  in  rows :
85-         lines .append (row_format .format (* row ))
86-     return  "\n " .join (lines )
87- 
88- 
8944#### TYPER UTILS 
9045
9146
@@ -150,3 +105,66 @@ class RepoType(str, Enum):
150105        help = "Git revision id which can be a branch name, a tag, or a commit hash." ,
151106    ),
152107]
108+ 
109+ 
110+ ### PyPI VERSION CHECKER 
111+ 
112+ 
113+ def  check_cli_update () ->  None :
114+     """ 
115+     Check whether a newer version of `huggingface_hub` is available on PyPI. 
116+ 
117+     If a newer version is found, notify the user and suggest updating. 
118+     If current version is a pre-release (e.g. `1.0.0.rc1`), or a dev version (e.g. `1.0.0.dev1`), no check is performed. 
119+ 
120+     This function is called at the entry point of the CLI. It only performs the check once every 24 hours, and any error 
121+     during the check is caught and logged, to avoid breaking the CLI. 
122+     """ 
123+     try :
124+         _check_cli_update ()
125+     except  Exception :
126+         # We don't want the CLI to fail on version checks, no matter the reason. 
127+         logger .debug ("Error while checking for CLI update." , exc_info = True )
128+ 
129+ 
130+ def  _check_cli_update () ->  None :
131+     current_version  =  importlib .metadata .version ("huggingface_hub" )
132+ 
133+     # Skip if current version is a pre-release or dev version 
134+     if  any (tag  in  current_version  for  tag  in  ["rc" , "dev" ]):
135+         return 
136+ 
137+     # Skip if already checked in the last 24 hours 
138+     if  os .path .exists (constants .CHECK_FOR_UPDATE_DONE_PATH ):
139+         mtime  =  os .path .getmtime (constants .CHECK_FOR_UPDATE_DONE_PATH )
140+         if  (time .time () -  mtime ) <  24  *  3600 :
141+             return 
142+ 
143+     # Touch the file to mark that we did the check now 
144+     Path (constants .CHECK_FOR_UPDATE_DONE_PATH ).touch ()
145+ 
146+     # Check latest version from PyPI 
147+     response  =  get_session ().get ("https://pypi.org/pypi/huggingface_hub/json" , timeout = 2 )
148+     hf_raise_for_status (response )
149+     data  =  response .json ()
150+     latest_version  =  data ["info" ]["version" ]
151+ 
152+     # If latest version is different from current, notify user 
153+     if  current_version  !=  latest_version :
154+         method  =  installation_method ()
155+         if  method  ==  "brew" :
156+             update_command  =  "brew upgrade huggingface-cli" 
157+         elif  method  ==  "hf_installer"  and  os .name  ==  "nt" :
158+             update_command  =  'powershell -NoProfile -Command "iwr -useb https://hf.co/cli/install.ps1 | iex"' 
159+         elif  method  ==  "hf_installer" :
160+             update_command  =  "curl -LsSf https://hf.co/cli/install.sh | sh -" 
161+         else :  # unknown => likely pip 
162+             update_command  =  "pip install -U huggingface_hub" 
163+ 
164+         click .echo (
165+             ANSI .yellow (
166+                 f"A new version of huggingface_hub ({ latest_version }  
167+                 f"You are using version { current_version } \n " 
168+                 f"To update, run: { ANSI .bold (update_command )} \n " ,
169+             )
170+         )
0 commit comments