How-to guides¶
Recipes for common tasks. For background on why directories differ across platforms, see Understanding platformdirs.
Common patterns¶
Creating directories safely¶
Always create parent directories before writing files:
from pathlib import Path
from platformdirs import user_data_path
data_dir = user_data_path("MyApp")
db_file = data_dir / "data.db"
# Create directory if it doesn't exist
db_file.parent.mkdir(parents=True, exist_ok=True)
# Now safe to write
db_file.write_bytes(b"...")
Or use ensure_exists=True to create directories automatically:
from platformdirs import PlatformDirs
dirs = PlatformDirs("MyApp", ensure_exists=True)
db_file = dirs.user_data_path / "data.db"
db_file.write_bytes(b"...") # Directory already exists
Placing a file in a user directory¶
place_config_file() returns the path of a file under user_config_dir and
creates the missing directories on the way with mode 0o700, as the XDG base directory specification asks. The process umask still applies to that mode.
Directories that already exist keep their mode, and you write the file yourself:
from platformdirs import PlatformDirs
dirs = PlatformDirs("MyApp")
path = dirs.place_config_file("profiles/default.toml")
path.write_text('theme = "dark"\n', encoding="utf-8")
place_data_file, place_cache_file, place_state_file, place_log_file and place_runtime_file do the
same for the matching user_*_dir. A name that is empty, absolute, has a drive or climbs out with .. raises
ValueError.
Handling write errors¶
Directory paths may not be writable due to permissions or disk space:
from platformdirs import user_data_path
from pathlib import Path
import tempfile
data_dir = user_data_path("MyApp")
data_file = data_dir / "data.json"
try:
data_dir.mkdir(parents=True, exist_ok=True)
data_file.write_text('{"key": "value"}')
except (OSError, PermissionError) as e:
# Fallback to temp directory
temp_dir = Path(tempfile.gettempdir()) / "MyApp"
temp_dir.mkdir(parents=True, exist_ok=True)
data_file = temp_dir / "data.json"
data_file.write_text('{"key": "value"}')
Checking directory writability¶
Test if a directory is writable before using it:
from platformdirs import user_cache_path
import os
cache_dir = user_cache_path("MyApp")
cache_dir.mkdir(parents=True, exist_ok=True)
if os.access(cache_dir, os.W_OK):
# Directory is writable
cache_file = cache_dir / "cache.dat"
cache_file.write_bytes(b"...")
else:
# Handle read-only directory
print(f"Warning: {cache_dir} is not writable")
Cleaning up cache¶
Implement cache cleanup based on age or size:
from platformdirs import user_cache_path
import time
cache_dir = user_cache_path("MyApp")
max_age_days = 30
if cache_dir.exists():
now = time.time()
for item in cache_dir.rglob("*"):
if item.is_file():
age_days = (now - item.stat().st_mtime) / 86400
if age_days > max_age_days:
item.unlink()
Versioned data migration¶
When using the version parameter, handle migration between versions:
from platformdirs import user_data_path
import shutil
current_version = "2.0"
previous_version = "1.0"
current_dir = user_data_path("MyApp", version=current_version)
previous_dir = user_data_path("MyApp", version=previous_version)
if not current_dir.exists() and previous_dir.exists():
# Migrate data from previous version
shutil.copytree(previous_dir, current_dir)
Merging config from multiple sources¶
Use iter_config_paths() to load site-wide defaults first, then overlay
user-specific overrides:
import json
from platformdirs import PlatformDirs
dirs = PlatformDirs("MyApp")
config = {}
# The iterators yield the user directory first, so apply them in reverse to let it win
for config_dir in reversed(list(dirs.iter_config_paths())):
config_file = config_dir / "config.json"
if config_file.exists():
config.update(json.loads(config_file.read_text()))
The same pattern works with iter_data_paths() for data files and
iter_config_dirs() for string paths.
Finding a file in user or site directories¶
Use find_config_file() to load the copy of a file that wins under the XDG Base
Directory precedence rule, user directory before
site directories, and find_config_files() to get every copy in that order:
from platformdirs import PlatformDirs
dirs = PlatformDirs("MyApp", "Acme")
if (config_file := dirs.find_config_file("config.toml")) is not None:
print(config_file.read_text())
for plugin_file in dirs.find_config_files("plugins/enabled.toml"):
print(plugin_file)
Both search the directories of iter_config_paths() and match regular files only,
so a directory named config.toml is skipped. They never create a directory, even with ensure_exists=True. The
name may contain subdirectories; an absolute path, a drive or a .. component raises ValueError. The same
pair exists for data, cache, state, log and runtime files, e.g.
find_data_file().
Testing code that uses platformdirs¶
Wrap the test in platformdirs.testing.isolated_dirs() to resolve every directory under a temporary root, so the
test never reads or writes the real home and system folders:
import json
from platformdirs import PlatformDirs
def my_app_init(dirs: PlatformDirs) -> dict:
config_file = dirs.user_config_path / "config.json"
if config_file.exists():
return json.loads(config_file.read_text())
return {}
# test_my_app.py
from platformdirs import PlatformDirs
from platformdirs.testing import isolated_dirs
def test_loads_config(tmp_path):
with isolated_dirs(tmp_path):
config_file = (
PlatformDirs("MyApp", ensure_exists=True).user_config_path / "config.json"
)
config_file.write_text('{"theme": "dark"}')
assert my_app_init(PlatformDirs("MyApp")) == {"theme": "dark"}
With pytest, request the platformdirs_isolated fixture instead. platformdirs registers it as a pytest plugin; it
isolates under tmp_path and yields the root:
def test_loads_config(platformdirs_isolated):
config_file = (
PlatformDirs("MyApp", ensure_exists=True).user_config_path / "config.json"
)
config_file.write_text('{"theme": "dark"}')
assert my_app_init(PlatformDirs("MyApp")) == {"theme": "dark"}
Each kind lands in <root>/<kind>, for example <root>/user_config/MyApp, and the module-level functions follow
the same layout. The redirection covers PlatformDirs in the current process only: a subprocess, or
another platform class such as Unix on macOS, still resolves the real directories.
Overriding directories with environment variables¶
On Linux/macOS, set XDG environment variables to redirect directories:
$ export XDG_CONFIG_HOME="$HOME/.myconfig"
$ python -c "from platformdirs import user_config_dir; print(user_config_dir('MyApp'))"
/home/user/.myconfig/MyApp
On Windows, use WIN_PD_OVERRIDE_* variables:
> set WIN_PD_OVERRIDE_LOCAL_APPDATA=X:\appdata
> python -c "from platformdirs import user_cache_dir; print(user_cache_dir('MyApp', 'Acme'))"
X:\appdata\Acme\MyApp\Cache
See XDG environment variables for the full list of supported XDG variables, and the Windows section for all
WIN_PD_OVERRIDE_* variables.
Platform-specific recipes¶
Windows: roaming vs local profiles¶
Use roaming=True for settings that should sync across domain-joined machines:
from platformdirs import user_config_path, user_cache_path
# Synced across domain computers
roaming_cfg = user_config_path("MyApp", roaming=True)
# Local to this machine
local_cache = user_cache_path("MyApp")
macOS: separating data and config¶
On macOS, user_data_dir and user_config_dir both resolve to ~/Library/Application Support/AppName. Use
subdirectories to separate concerns:
from platformdirs import user_data_path
app_dir = user_data_path("MyApp")
config_dir = app_dir / "config"
databases_dir = app_dir / "databases"
Linux: falling back from site to user config¶
Site directories require root privileges. Fall back to user directories for normal users:
from platformdirs import site_config_path, user_config_path
import os
site_cfg = site_config_path("MyApp") / "defaults.conf"
if os.access(site_cfg.parent, os.W_OK):
site_cfg.write_text("[defaults]\n")
else:
user_cfg = user_config_path("MyApp") / "config.conf"
user_cfg.parent.mkdir(parents=True, exist_ok=True)
user_cfg.write_text("[user_settings]\n")
Linux: redirecting user directories when running as root¶
Use use_site_for_root=True so system services write to /usr/local/share instead of /root:
from platformdirs import PlatformDirs
import os
if os.geteuid() == 0:
dirs = PlatformDirs("myservice", use_site_for_root=True)
# user_data_dir now returns /usr/local/share/myservice
else:
dirs = PlatformDirs("myservice")
# user_data_dir returns ~/.local/share/myservice