77import logging
88import logging .handlers
99import os
10+ from pathlib import Path
11+ import platform
1012from typing import Tuple , cast
1113
1214from io import StringIO
1921file_handler = None
2022
2123
22- def _create_log_handlers ():
24+ def _create_log_handlers (create_file_handler = True ):
2325 """ Creates but does not attach the log handlers. """
2426 global console_handler , file_handler
2527 if console_handler is not None or file_handler is not None :
@@ -32,12 +34,13 @@ def _create_log_handlers():
3234 console_handler = logging .StreamHandler ()
3335 console_handler .setFormatter (output_formatter )
3436
35- one_mb = 2 ** 20
36- log_path = os .path .join (cache_directory , "openml_python.log" )
37- file_handler = logging .handlers .RotatingFileHandler (
38- log_path , maxBytes = one_mb , backupCount = 1 , delay = True
39- )
40- file_handler .setFormatter (output_formatter )
37+ if create_file_handler :
38+ one_mb = 2 ** 20
39+ log_path = os .path .join (cache_directory , "openml_python.log" )
40+ file_handler = logging .handlers .RotatingFileHandler (
41+ log_path , maxBytes = one_mb , backupCount = 1 , delay = True
42+ )
43+ file_handler .setFormatter (output_formatter )
4144
4245
4346def _convert_log_levels (log_level : int ) -> Tuple [int , int ]:
@@ -83,15 +86,18 @@ def set_file_log_level(file_output_level: int):
8386
8487# Default values (see also https://github.com/openml/OpenML/wiki/Client-API-Standards)
8588_defaults = {
86- "apikey" : None ,
89+ "apikey" : "" ,
8790 "server" : "https://www.openml.org/api/v1/xml" ,
88- "cachedir" : os .path .expanduser (os .path .join ("~" , ".openml" , "cache" )),
91+ "cachedir" : (
92+ os .environ .get ("XDG_CACHE_HOME" , os .path .join ("~" , ".cache" , "openml" ,))
93+ if platform .system () == "Linux"
94+ else os .path .join ("~" , ".openml" )
95+ ),
8996 "avoid_duplicate_runs" : "True" ,
90- "connection_n_retries" : 10 ,
91- "max_retries" : 20 ,
97+ "connection_n_retries" : "10" ,
98+ "max_retries" : "20" ,
9299}
93100
94- config_file = os .path .expanduser (os .path .join ("~" , ".openml" , "config" ))
95101
96102# Default values are actually added here in the _setup() function which is
97103# called at the end of this module
@@ -116,8 +122,8 @@ def get_server_base_url() -> str:
116122avoid_duplicate_runs = True if _defaults ["avoid_duplicate_runs" ] == "True" else False
117123
118124# Number of retries if the connection breaks
119- connection_n_retries = _defaults ["connection_n_retries" ]
120- max_retries = _defaults ["max_retries" ]
125+ connection_n_retries = int ( _defaults ["connection_n_retries" ])
126+ max_retries = int ( _defaults ["max_retries" ])
121127
122128
123129class ConfigurationForExamples :
@@ -187,62 +193,78 @@ def _setup():
187193 global connection_n_retries
188194 global max_retries
189195
190- # read config file, create cache directory
191- try :
192- os .mkdir (os .path .expanduser (os .path .join ("~" , ".openml" )))
193- except FileExistsError :
194- # For other errors, we want to propagate the error as openml does not work without cache
195- pass
196+ if platform .system () == "Linux" :
197+ config_dir = Path (os .environ .get ("XDG_CONFIG_HOME" , Path ("~" ) / ".config" / "openml" ))
198+ else :
199+ config_dir = Path ("~" ) / ".openml"
200+ # Still use os.path.expanduser to trigger the mock in the unit test
201+ config_dir = Path (os .path .expanduser (config_dir ))
202+ config_file = config_dir / "config"
203+
204+ # read config file, create directory for config file
205+ if not os .path .exists (config_dir ):
206+ try :
207+ os .mkdir (config_dir )
208+ cache_exists = True
209+ except PermissionError :
210+ cache_exists = False
211+ else :
212+ cache_exists = True
213+
214+ if cache_exists :
215+ _create_log_handlers ()
216+ else :
217+ _create_log_handlers (create_file_handler = False )
218+ openml_logger .warning (
219+ "No permission to create OpenML directory at %s! This can result in OpenML-Python "
220+ "not working properly." % config_dir
221+ )
196222
197- config = _parse_config ()
223+ config = _parse_config (config_file )
198224 apikey = config .get ("FAKE_SECTION" , "apikey" )
199225 server = config .get ("FAKE_SECTION" , "server" )
200226
201- short_cache_dir = config .get ("FAKE_SECTION" , "cachedir" )
202- cache_directory = os .path .expanduser (short_cache_dir )
227+ cache_dir = config .get ("FAKE_SECTION" , "cachedir" )
228+ cache_directory = os .path .expanduser (cache_dir )
203229
204230 # create the cache subdirectory
205- try :
206- os .mkdir (cache_directory )
207- except FileExistsError :
208- # For other errors, we want to propagate the error as openml does not work without cache
209- pass
231+ if not os .path .exists (cache_directory ):
232+ try :
233+ os .mkdir (cache_directory )
234+ except PermissionError :
235+ openml_logger .warning (
236+ "No permission to create openml cache directory at %s! This can result in "
237+ "OpenML-Python not working properly." % cache_directory
238+ )
210239
211240 avoid_duplicate_runs = config .getboolean ("FAKE_SECTION" , "avoid_duplicate_runs" )
212- connection_n_retries = config .get ("FAKE_SECTION" , "connection_n_retries" )
213- max_retries = config .get ("FAKE_SECTION" , "max_retries" )
241+ connection_n_retries = int ( config .get ("FAKE_SECTION" , "connection_n_retries" ) )
242+ max_retries = int ( config .get ("FAKE_SECTION" , "max_retries" ) )
214243 if connection_n_retries > max_retries :
215244 raise ValueError (
216245 "A higher number of retries than {} is not allowed to keep the "
217246 "server load reasonable" .format (max_retries )
218247 )
219248
220249
221- def _parse_config ():
250+ def _parse_config (config_file : str ):
222251 """ Parse the config file, set up defaults. """
223252 config = configparser .RawConfigParser (defaults = _defaults )
224253
225- if not os .path .exists (config_file ):
226- # Create an empty config file if there was none so far
227- fh = open (config_file , "w" )
228- fh .close ()
229- logger .info (
230- "Could not find a configuration file at %s. Going to "
231- "create an empty file there." % config_file
232- )
233-
254+ # The ConfigParser requires a [SECTION_HEADER], which we do not expect in our config file.
255+ # Cheat the ConfigParser module by adding a fake section header
256+ config_file_ = StringIO ()
257+ config_file_ .write ("[FAKE_SECTION]\n " )
234258 try :
235- # The ConfigParser requires a [SECTION_HEADER], which we do not expect in our config file.
236- # Cheat the ConfigParser module by adding a fake section header
237- config_file_ = StringIO ()
238- config_file_ .write ("[FAKE_SECTION]\n " )
239259 with open (config_file ) as fh :
240260 for line in fh :
241261 config_file_ .write (line )
242- config_file_ . seek ( 0 )
243- config . read_file ( config_file_ )
262+ except FileNotFoundError :
263+ logger . info ( "No config file found at %s, using default configuration." , config_file )
244264 except OSError as e :
245- logger .info ("Error opening file %s: %s" , config_file , e .message )
265+ logger .info ("Error opening file %s: %s" , config_file , e .args [0 ])
266+ config_file_ .seek (0 )
267+ config .read_file (config_file_ )
246268 return config
247269
248270
@@ -257,11 +279,7 @@ def get_cache_directory():
257279 """
258280 url_suffix = urlparse (server ).netloc
259281 reversed_url_suffix = os .sep .join (url_suffix .split ("." )[::- 1 ])
260- if not cache_directory :
261- _cachedir = _defaults (cache_directory )
262- else :
263- _cachedir = cache_directory
264- _cachedir = os .path .join (_cachedir , reversed_url_suffix )
282+ _cachedir = os .path .join (cache_directory , reversed_url_suffix )
265283 return _cachedir
266284
267285
@@ -297,4 +315,3 @@ def set_cache_directory(cachedir):
297315]
298316
299317_setup ()
300- _create_log_handlers ()
0 commit comments