1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright (c) 2005-2010 ActiveState Software Inc. 4# Copyright (c) 2013 Eddy Petrișor 5 6# flake8: noqa 7 8""" 9This file is directly from 10https://github.com/ActiveState/appdirs/blob/3fe6a83776843a46f20c2e5587afcffe05e03b39/appdirs.py 11 12The license of https://github.com/ActiveState/appdirs copied below: 13 14 15# This is the MIT license 16 17Copyright (c) 2010 ActiveState Software Inc. 18 19Permission is hereby granted, free of charge, to any person obtaining a 20copy of this software and associated documentation files (the 21"Software"), to deal in the Software without restriction, including 22without limitation the rights to use, copy, modify, merge, publish, 23distribute, sublicense, and/or sell copies of the Software, and to 24permit persons to whom the Software is furnished to do so, subject to 25the following conditions: 26 27The above copyright notice and this permission notice shall be included 28in all copies or substantial portions of the Software. 29 30THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 31OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 32MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 33IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 34CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 35TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 36SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 37""" 38 39"""Utilities for determining application-specific dirs. 40 41See <https://github.com/ActiveState/appdirs> for details and usage. 42""" 43# Dev Notes: 44# - MSDN on where to store app data files: 45# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 46# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html 47# - XDG spec for Un*x: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html 48 49__version__ = "1.4.4" 50__version_info__ = tuple(int(segment) for segment in __version__.split(".")) 51 52 53import os 54import sys 55 56 57unicode = str 58 59if sys.platform.startswith("java"): 60 import platform 61 62 os_name = platform.java_ver()[3][0] 63 if os_name.startswith("Windows"): # "Windows XP", "Windows 7", etc. 64 system = "win32" 65 elif os_name.startswith("Mac"): # "Mac OS X", etc. 66 system = "darwin" 67 else: # "Linux", "SunOS", "FreeBSD", etc. 68 # Setting this to "linux2" is not ideal, but only Windows or Mac 69 # are actually checked for and the rest of the module expects 70 # *sys.platform* style strings. 71 system = "linux2" 72else: 73 system = sys.platform 74 75 76def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): 77 r"""Return full path to the user-specific data dir for this application. 78 79 "appname" is the name of application. 80 If None, just the system directory is returned. 81 "appauthor" (only used on Windows) is the name of the 82 appauthor or distributing body for this application. Typically 83 it is the owning company name. This falls back to appname. You may 84 pass False to disable it. 85 "version" is an optional version path element to append to the 86 path. You might want to use this if you want multiple versions 87 of your app to be able to run independently. If used, this 88 would typically be "<major>.<minor>". 89 Only applied when appname is present. 90 "roaming" (boolean, default False) can be set True to use the Windows 91 roaming appdata directory. That means that for users on a Windows 92 network setup for roaming profiles, this user data will be 93 sync'd on login. See 94 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> 95 for a discussion of issues. 96 97 Typical user data directories are: 98 Mac OS X: ~/Library/Application Support/<AppName> 99 Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined 100 Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName> 101 Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName> 102 Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName> 103 Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName> 104 105 For Unix, we follow the XDG spec and support $XDG_DATA_HOME. 106 That means, by default "~/.local/share/<AppName>". 107 """ 108 if system == "win32": 109 if appauthor is None: 110 appauthor = appname 111 const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" 112 path = os.path.normpath(_get_win_folder(const)) 113 if appname: 114 if appauthor is not False: 115 path = os.path.join(path, appauthor, appname) 116 else: 117 path = os.path.join(path, appname) 118 elif system == "darwin": 119 path = os.path.expanduser("~/Library/Application Support/") 120 if appname: 121 path = os.path.join(path, appname) 122 else: 123 path = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) 124 if appname: 125 path = os.path.join(path, appname) 126 if appname and version: 127 path = os.path.join(path, version) 128 return path 129 130 131def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): 132 r"""Return full path to the user-shared data dir for this application. 133 134 "appname" is the name of application. 135 If None, just the system directory is returned. 136 "appauthor" (only used on Windows) is the name of the 137 appauthor or distributing body for this application. Typically 138 it is the owning company name. This falls back to appname. You may 139 pass False to disable it. 140 "version" is an optional version path element to append to the 141 path. You might want to use this if you want multiple versions 142 of your app to be able to run independently. If used, this 143 would typically be "<major>.<minor>". 144 Only applied when appname is present. 145 "multipath" is an optional parameter only applicable to *nix 146 which indicates that the entire list of data dirs should be 147 returned. By default, the first item from XDG_DATA_DIRS is 148 returned, or '/usr/local/share/<AppName>', 149 if XDG_DATA_DIRS is not set 150 151 Typical site data directories are: 152 Mac OS X: /Library/Application Support/<AppName> 153 Unix: /usr/local/share/<AppName> or /usr/share/<AppName> 154 Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName> 155 Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) 156 Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7. 157 158 For Unix, this is using the $XDG_DATA_DIRS[0] default. 159 160 WARNING: Do not use this on Windows. See the Vista-Fail note above for why. 161 """ 162 if system == "win32": 163 if appauthor is None: 164 appauthor = appname 165 path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) 166 if appname: 167 if appauthor is not False: 168 path = os.path.join(path, appauthor, appname) 169 else: 170 path = os.path.join(path, appname) 171 elif system == "darwin": 172 path = os.path.expanduser("/Library/Application Support") 173 if appname: 174 path = os.path.join(path, appname) 175 else: 176 # XDG default for $XDG_DATA_DIRS 177 # only first, if multipath is False 178 path = os.getenv( 179 "XDG_DATA_DIRS", os.pathsep.join(["/usr/local/share", "/usr/share"]) 180 ) 181 pathlist = [ 182 os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) 183 ] 184 if appname: 185 if version: 186 appname = os.path.join(appname, version) 187 pathlist = [os.sep.join([x, appname]) for x in pathlist] 188 189 if multipath: 190 path = os.pathsep.join(pathlist) 191 else: 192 path = pathlist[0] 193 return path 194 195 if appname and version: 196 path = os.path.join(path, version) 197 return path 198 199 200def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): 201 r"""Return full path to the user-specific config dir for this application. 202 203 "appname" is the name of application. 204 If None, just the system directory is returned. 205 "appauthor" (only used on Windows) is the name of the 206 appauthor or distributing body for this application. Typically 207 it is the owning company name. This falls back to appname. You may 208 pass False to disable it. 209 "version" is an optional version path element to append to the 210 path. You might want to use this if you want multiple versions 211 of your app to be able to run independently. If used, this 212 would typically be "<major>.<minor>". 213 Only applied when appname is present. 214 "roaming" (boolean, default False) can be set True to use the Windows 215 roaming appdata directory. That means that for users on a Windows 216 network setup for roaming profiles, this user data will be 217 sync'd on login. See 218 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> 219 for a discussion of issues. 220 221 Typical user config directories are: 222 Mac OS X: ~/Library/Preferences/<AppName> 223 Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined 224 Win *: same as user_data_dir 225 226 For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. 227 That means, by default "~/.config/<AppName>". 228 """ 229 if system == "win32": 230 path = user_data_dir(appname, appauthor, None, roaming) 231 elif system == "darwin": 232 path = os.path.expanduser("~/Library/Preferences/") 233 if appname: 234 path = os.path.join(path, appname) 235 else: 236 path = os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) 237 if appname: 238 path = os.path.join(path, appname) 239 if appname and version: 240 path = os.path.join(path, version) 241 return path 242 243 244def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): 245 r"""Return full path to the user-shared data dir for this application. 246 247 "appname" is the name of application. 248 If None, just the system directory is returned. 249 "appauthor" (only used on Windows) is the name of the 250 appauthor or distributing body for this application. Typically 251 it is the owning company name. This falls back to appname. You may 252 pass False to disable it. 253 "version" is an optional version path element to append to the 254 path. You might want to use this if you want multiple versions 255 of your app to be able to run independently. If used, this 256 would typically be "<major>.<minor>". 257 Only applied when appname is present. 258 "multipath" is an optional parameter only applicable to *nix 259 which indicates that the entire list of config dirs should be 260 returned. By default, the first item from XDG_CONFIG_DIRS is 261 returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set 262 263 Typical site config directories are: 264 Mac OS X: same as site_data_dir 265 Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in 266 $XDG_CONFIG_DIRS 267 Win *: same as site_data_dir 268 Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) 269 270 For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False 271 272 WARNING: Do not use this on Windows. See the Vista-Fail note above for why. 273 """ 274 if system == "win32": 275 path = site_data_dir(appname, appauthor) 276 if appname and version: 277 path = os.path.join(path, version) 278 elif system == "darwin": 279 path = os.path.expanduser("/Library/Preferences") 280 if appname: 281 path = os.path.join(path, appname) 282 else: 283 # XDG default for $XDG_CONFIG_DIRS 284 # only first, if multipath is False 285 path = os.getenv("XDG_CONFIG_DIRS", "/etc/xdg") 286 pathlist = [ 287 os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) 288 ] 289 if appname: 290 if version: 291 appname = os.path.join(appname, version) 292 pathlist = [os.sep.join([x, appname]) for x in pathlist] 293 294 if multipath: 295 path = os.pathsep.join(pathlist) 296 else: 297 path = pathlist[0] 298 return path 299 300 301def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): 302 r"""Return full path to the user-specific cache dir for this application. 303 304 "appname" is the name of application. 305 If None, just the system directory is returned. 306 "appauthor" (only used on Windows) is the name of the 307 appauthor or distributing body for this application. Typically 308 it is the owning company name. This falls back to appname. You may 309 pass False to disable it. 310 "version" is an optional version path element to append to the 311 path. You might want to use this if you want multiple versions 312 of your app to be able to run independently. If used, this 313 would typically be "<major>.<minor>". 314 Only applied when appname is present. 315 "opinion" (boolean) can be False to disable the appending of 316 "Cache" to the base app data dir for Windows. See 317 discussion below. 318 319 Typical user cache directories are: 320 Mac OS X: ~/Library/Caches/<AppName> 321 Unix: ~/.cache/<AppName> (XDG default) 322 Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache 323 Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache 324 325 On Windows the only suggestion in the MSDN docs is that local settings go in 326 the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming 327 app data dir (the default returned by `user_data_dir` above). Apps typically 328 put cache data somewhere *under* the given dir here. Some examples: 329 ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache 330 ...\Acme\SuperApp\Cache\1.0 331 OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. 332 This can be disabled with the `opinion=False` option. 333 """ 334 if system == "win32": 335 if appauthor is None: 336 appauthor = appname 337 path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) 338 if appname: 339 if appauthor is not False: 340 path = os.path.join(path, appauthor, appname) 341 else: 342 path = os.path.join(path, appname) 343 if opinion: 344 path = os.path.join(path, "Cache") 345 elif system == "darwin": 346 path = os.path.expanduser("~/Library/Caches") 347 if appname: 348 path = os.path.join(path, appname) 349 else: 350 path = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")) 351 if appname: 352 path = os.path.join(path, appname) 353 if appname and version: 354 path = os.path.join(path, version) 355 return path 356 357 358def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): 359 r"""Return full path to the user-specific state dir for this application. 360 361 "appname" is the name of application. 362 If None, just the system directory is returned. 363 "appauthor" (only used on Windows) is the name of the 364 appauthor or distributing body for this application. Typically 365 it is the owning company name. This falls back to appname. You may 366 pass False to disable it. 367 "version" is an optional version path element to append to the 368 path. You might want to use this if you want multiple versions 369 of your app to be able to run independently. If used, this 370 would typically be "<major>.<minor>". 371 Only applied when appname is present. 372 "roaming" (boolean, default False) can be set True to use the Windows 373 roaming appdata directory. That means that for users on a Windows 374 network setup for roaming profiles, this user data will be 375 sync'd on login. See 376 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> 377 for a discussion of issues. 378 379 Typical user state directories are: 380 Mac OS X: same as user_data_dir 381 Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined 382 Win *: same as user_data_dir 383 384 For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state> 385 to extend the XDG spec and support $XDG_STATE_HOME. 386 387 That means, by default "~/.local/state/<AppName>". 388 """ 389 if system in ["win32", "darwin"]: 390 path = user_data_dir(appname, appauthor, None, roaming) 391 else: 392 path = os.getenv("XDG_STATE_HOME", os.path.expanduser("~/.local/state")) 393 if appname: 394 path = os.path.join(path, appname) 395 if appname and version: 396 path = os.path.join(path, version) 397 return path 398 399 400def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): 401 r"""Return full path to the user-specific log dir for this application. 402 403 "appname" is the name of application. 404 If None, just the system directory is returned. 405 "appauthor" (only used on Windows) is the name of the 406 appauthor or distributing body for this application. Typically 407 it is the owning company name. This falls back to appname. You may 408 pass False to disable it. 409 "version" is an optional version path element to append to the 410 path. You might want to use this if you want multiple versions 411 of your app to be able to run independently. If used, this 412 would typically be "<major>.<minor>". 413 Only applied when appname is present. 414 "opinion" (boolean) can be False to disable the appending of 415 "Logs" to the base app data dir for Windows, and "log" to the 416 base cache dir for Unix. See discussion below. 417 418 Typical user log directories are: 419 Mac OS X: ~/Library/Logs/<AppName> 420 Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined 421 Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs 422 Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs 423 424 On Windows the only suggestion in the MSDN docs is that local settings 425 go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in 426 examples of what some windows apps use for a logs dir.) 427 428 OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` 429 value for Windows and appends "log" to the user cache dir for Unix. 430 This can be disabled with the `opinion=False` option. 431 """ 432 if system == "darwin": 433 path = os.path.join(os.path.expanduser("~/Library/Logs"), appname) 434 elif system == "win32": 435 path = user_data_dir(appname, appauthor, version) 436 version = False 437 if opinion: 438 path = os.path.join(path, "Logs") 439 else: 440 path = user_cache_dir(appname, appauthor, version) 441 version = False 442 if opinion: 443 path = os.path.join(path, "log") 444 if appname and version: 445 path = os.path.join(path, version) 446 return path 447 448 449class AppDirs(object): 450 """Convenience wrapper for getting application dirs.""" 451 452 def __init__( 453 self, appname=None, appauthor=None, version=None, roaming=False, multipath=False 454 ): 455 self.appname = appname 456 self.appauthor = appauthor 457 self.version = version 458 self.roaming = roaming 459 self.multipath = multipath 460 461 @property 462 def user_data_dir(self): 463 return user_data_dir( 464 self.appname, self.appauthor, version=self.version, roaming=self.roaming 465 ) 466 467 @property 468 def site_data_dir(self): 469 return site_data_dir( 470 self.appname, self.appauthor, version=self.version, multipath=self.multipath 471 ) 472 473 @property 474 def user_config_dir(self): 475 return user_config_dir( 476 self.appname, self.appauthor, version=self.version, roaming=self.roaming 477 ) 478 479 @property 480 def site_config_dir(self): 481 return site_config_dir( 482 self.appname, self.appauthor, version=self.version, multipath=self.multipath 483 ) 484 485 @property 486 def user_cache_dir(self): 487 return user_cache_dir(self.appname, self.appauthor, version=self.version) 488 489 @property 490 def user_state_dir(self): 491 return user_state_dir(self.appname, self.appauthor, version=self.version) 492 493 @property 494 def user_log_dir(self): 495 return user_log_dir(self.appname, self.appauthor, version=self.version) 496 497 498# ---- internal support stuff 499 500 501def _get_win_folder_from_registry(csidl_name): 502 """This is a fallback technique at best. I'm not sure if using the 503 registry for this guarantees us the correct answer for all CSIDL_* 504 names. 505 """ 506 import winreg as _winreg 507 508 shell_folder_name = { 509 "CSIDL_APPDATA": "AppData", 510 "CSIDL_COMMON_APPDATA": "Common AppData", 511 "CSIDL_LOCAL_APPDATA": "Local AppData", 512 }[csidl_name] 513 514 key = _winreg.OpenKey( 515 _winreg.HKEY_CURRENT_USER, 516 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", 517 ) 518 dir, type = _winreg.QueryValueEx(key, shell_folder_name) 519 return dir 520 521 522def _get_win_folder_with_pywin32(csidl_name): 523 from win32com.shell import shell, shellcon 524 525 dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) 526 # Try to make this a unicode path because SHGetFolderPath does 527 # not return unicode strings when there is unicode data in the 528 # path. 529 try: 530 dir = unicode(dir) 531 532 # Downgrade to short path name if have highbit chars. See 533 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 534 has_high_char = False 535 for c in dir: 536 if ord(c) > 255: 537 has_high_char = True 538 break 539 if has_high_char: 540 try: 541 import win32api 542 543 dir = win32api.GetShortPathName(dir) 544 except ImportError: 545 pass 546 except UnicodeError: 547 pass 548 return dir 549 550 551def _get_win_folder_with_ctypes(csidl_name): 552 import ctypes 553 554 csidl_const = { 555 "CSIDL_APPDATA": 26, 556 "CSIDL_COMMON_APPDATA": 35, 557 "CSIDL_LOCAL_APPDATA": 28, 558 }[csidl_name] 559 560 buf = ctypes.create_unicode_buffer(1024) 561 ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) 562 563 # Downgrade to short path name if have highbit chars. See 564 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 565 has_high_char = False 566 for c in buf: 567 if ord(c) > 255: 568 has_high_char = True 569 break 570 if has_high_char: 571 buf2 = ctypes.create_unicode_buffer(1024) 572 if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): 573 buf = buf2 574 575 return buf.value 576 577 578def _get_win_folder_with_jna(csidl_name): 579 import array 580 581 from com.sun import jna 582 from com.sun.jna.platform import win32 583 584 buf_size = win32.WinDef.MAX_PATH * 2 585 buf = array.zeros("c", buf_size) 586 shell = win32.Shell32.INSTANCE 587 shell.SHGetFolderPath( 588 None, 589 getattr(win32.ShlObj, csidl_name), 590 None, 591 win32.ShlObj.SHGFP_TYPE_CURRENT, 592 buf, 593 ) 594 dir = jna.Native.toString(buf.tostring()).rstrip("\0") 595 596 # Downgrade to short path name if have highbit chars. See 597 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 598 has_high_char = False 599 for c in dir: 600 if ord(c) > 255: 601 has_high_char = True 602 break 603 if has_high_char: 604 buf = array.zeros("c", buf_size) 605 kernel = win32.Kernel32.INSTANCE 606 if kernel.GetShortPathName(dir, buf, buf_size): 607 dir = jna.Native.toString(buf.tostring()).rstrip("\0") 608 609 return dir 610 611 612if system == "win32": 613 try: 614 import win32com.shell 615 616 _get_win_folder = _get_win_folder_with_pywin32 617 except ImportError: 618 try: 619 from ctypes import windll 620 621 _get_win_folder = _get_win_folder_with_ctypes 622 except ImportError: 623 try: 624 import com.sun.jna 625 626 _get_win_folder = _get_win_folder_with_jna 627 except ImportError: 628 _get_win_folder = _get_win_folder_from_registry 629 630 631# ---- self test code 632 633if __name__ == "__main__": 634 appname = "MyApp" 635 appauthor = "MyCompany" 636 637 props = ( 638 "user_data_dir", 639 "user_config_dir", 640 "user_cache_dir", 641 "user_state_dir", 642 "user_log_dir", 643 "site_data_dir", 644 "site_config_dir", 645 ) 646 647 print(f"-- app dirs {__version__} --") 648 649 print("-- app dirs (with optional 'version')") 650 dirs = AppDirs(appname, appauthor, version="1.0") 651 for prop in props: 652 print(f"{prop}: {getattr(dirs, prop)}") 653 654 print("\n-- app dirs (without optional 'version')") 655 dirs = AppDirs(appname, appauthor) 656 for prop in props: 657 print(f"{prop}: {getattr(dirs, prop)}") 658 659 print("\n-- app dirs (without optional 'appauthor')") 660 dirs = AppDirs(appname) 661 for prop in props: 662 print(f"{prop}: {getattr(dirs, prop)}") 663 664 print("\n-- app dirs (with disabled 'appauthor')") 665 dirs = AppDirs(appname, appauthor=False) 666 for prop in props: 667 print(f"{prop}: {getattr(dirs, prop)}") 668