diff --git a/.proselintrc.json b/.proselintrc.json new file mode 100644 index 0000000..d94a1c9 --- /dev/null +++ b/.proselintrc.json @@ -0,0 +1,6 @@ +{ + "checks": { + "lexical_illusions": false, + "typography": false + } +} diff --git a/docs/howto.rst b/docs/howto.rst new file mode 100644 index 0000000..c377c7b --- /dev/null +++ b/docs/howto.rst @@ -0,0 +1,297 @@ +How-to guides +============= + +This page provides recipes for common tasks and platform-specific guidance. + +Common patterns +--------------- + +.. _creating-directories-safely: + +Creating directories safely +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Always create parent directories before writing files: + +.. code-block:: python + + 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: + +.. code-block:: python + + 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 + +Handling write errors +~~~~~~~~~~~~~~~~~~~~~ + +Directory paths may not be writable due to permissions or disk space: + +.. code-block:: python + + 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: + +.. code-block:: python + + 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: + +.. code-block:: python + + 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: + +Versioned data migration +~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the ``version`` parameter, handle migration between versions: + +.. code-block:: python + + 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 + current_dir.mkdir(parents=True, exist_ok=True) + for item in previous_dir.iterdir(): + shutil.copy2(item, current_dir / item.name) + +Platform-specific considerations +--------------------------------- + +Windows +~~~~~~~ + +Windows Store Python sandbox +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Applications installed via the Microsoft Store run in a restricted sandbox environment. +``platformdirs`` handles this automatically, but be aware that: + +- File system access is limited to specific directories. +- Some APIs may return sandbox-specific paths. +- Network access may require additional permissions. + +Roaming vs local profiles +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use ``roaming=True`` for settings that should sync across domain-joined machines. Use local +(default) for machine-specific data like caches. + +.. code-block:: python + + 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") + +Environment variable overrides +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Windows supports environment variable overrides for certain directories: + +- ``WIN_PD_OVERRIDE_*`` - Override specific directory types +- ``PLATFORMDIRS_*`` - Alternative override mechanism + +See the source code for complete list of supported variables. + +macOS +~~~~~ + +Data and config location +^^^^^^^^^^^^^^^^^^^^^^^^^ + +On macOS, ``user_data_dir`` and ``user_config_dir`` both return +``~/Library/Application Support/AppName`` by default. Use subdirectories to separate concerns: + +.. code-block:: python + + from platformdirs import user_data_path + + app_dir = user_data_path("MyApp") + config_dir = app_dir / "config" + databases_dir = app_dir / "databases" + +XDG support on macOS +^^^^^^^^^^^^^^^^^^^^ + +macOS now supports XDG environment variables. If ``XDG_DATA_HOME`` is set, ``platformdirs`` +will honor it instead of using ``~/Library``: + +.. code-block:: python + + import os + from platformdirs import user_data_dir + + # Without XDG + user_data_dir("MyApp") # ~/Library/Application Support/MyApp + + # With XDG + os.environ["XDG_DATA_HOME"] = os.path.expanduser("~/.local/share") + user_data_dir("MyApp") # ~/.local/share/MyApp + +Linux/Unix +~~~~~~~~~~ + +Permission requirements +^^^^^^^^^^^^^^^^^^^^^^^^ + +Writing to ``site_*`` directories typically requires root privileges. Normal users can only +read from these locations: + +.. code-block:: python + + from platformdirs import site_config_path, user_config_path + import os + + site_cfg = site_config_path("MyApp") / "defaults.conf" + + # Check if we can write to site config + if os.access(site_cfg.parent, os.W_OK): + # Running as root or have special permissions + site_cfg.write_text("[defaults]\n") + else: + # Normal user - use user config instead + user_cfg = user_config_path("MyApp") / "config.conf" + user_cfg.parent.mkdir(parents=True, exist_ok=True) + user_cfg.write_text("[user_settings]\n") + +XDG Base Directory Specification +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``platformdirs`` fully supports the XDG Base Directory Specification. Users can override +default paths by setting environment variables: + +- ``XDG_DATA_HOME`` - defaults to ``~/.local/share`` +- ``XDG_CONFIG_HOME`` - defaults to ``~/.config`` +- ``XDG_CACHE_HOME`` - defaults to ``~/.cache`` +- ``XDG_STATE_HOME`` - defaults to ``~/.local/state`` +- ``XDG_RUNTIME_DIR`` - defaults to ``/run/user/$UID`` +- ``XDG_DATA_DIRS`` - defaults to ``/usr/local/share:/usr/share`` +- ``XDG_CONFIG_DIRS`` - defaults to ``/etc/xdg`` + +Running as root +^^^^^^^^^^^^^^^ + +When running as root (uid 0), ``user_*`` directories default to root's home directory +(``/root``). Use ``use_site_for_root=True`` to redirect to system directories instead: + +.. code-block:: python + + from platformdirs import PlatformDirs + import os + + # System service that should use /var/lib instead of /root + 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 + +Android +~~~~~~~ + +App-specific storage +^^^^^^^^^^^^^^^^^^^^ + +All directories are within your app's private storage (``/data/data//``). +Data is automatically removed when the app is uninstalled. + +.. code-block:: python + + from platformdirs import user_data_path + + # Returns /data/data/com.example.myapp/files/MyApp + data_dir = user_data_path("MyApp") + +External storage not supported +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``platformdirs`` does not provide paths for external/shared storage (SD cards, +``/storage/emulated/0/``). Use Android-specific APIs for shared storage: + +.. code-block:: python + + # For shared/external storage on Android, use Android APIs directly + # platformdirs only handles app-private directories + +Storage permissions +^^^^^^^^^^^^^^^^^^^ + +App-private directories provided by ``platformdirs`` don't require storage permissions. +For external storage access, your app needs appropriate Android permissions. diff --git a/docs/index.rst b/docs/index.rst index 94aec98..36fa09e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,8 @@ Features :caption: Contents usage + howto + parameters api platforms changelog diff --git a/docs/parameters.rst b/docs/parameters.rst new file mode 100644 index 0000000..c7452cd --- /dev/null +++ b/docs/parameters.rst @@ -0,0 +1,206 @@ +Parameter reference +=================== + +This page documents all parameters accepted by ``platformdirs`` functions and the +:class:`~platformdirs.PlatformDirs` class. + +``appname`` +----------- + +The name of your application. Used as a subdirectory in all app-specific paths. +When ``None``, the base platform directory is returned without any app-specific subdirectory. + +.. code-block:: pycon + + >>> from platformdirs import user_data_dir + >>> user_data_dir("SuperApp") + '/Users/trentm/Library/Application Support/SuperApp' + >>> user_data_dir() + '/Users/trentm/Library/Application Support' + +**Type**: ``str | None`` + +**Default**: ``None`` + +``appauthor`` +------------- + +The app author or distributing organization. On Windows, this adds an additional parent directory: + +.. code-block:: pycon + + >>> user_data_dir("SuperApp", "Acme") # on Windows + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' + +Set to ``False`` to suppress the author directory even on Windows: + +.. code-block:: pycon + + >>> user_data_dir("SuperApp", False) # on Windows + 'C:\\Users\\trentm\\AppData\\Local\\SuperApp' + +On non-Windows platforms, ``appauthor`` is ignored. + +**Type**: ``str | Literal[False] | None`` + +**Default**: ``None`` + +``version`` +----------- + +Appends a version subdirectory. Useful for running multiple app versions side by side: + +.. code-block:: pycon + + >>> from platformdirs import PlatformDirs + >>> dirs = PlatformDirs("SuperApp", "Acme", version="1.0") + >>> dirs.user_data_dir + '/Users/trentm/Library/Application Support/SuperApp/1.0' + >>> dirs.user_cache_dir + '/Users/trentm/Library/Caches/SuperApp/1.0' + +Be wary of using this for configuration files; you will need to handle migrating configuration +files between versions manually. See :ref:`versioned-data-migration` for guidance. + +**Type**: ``str | None`` + +**Default**: ``None`` + +``roaming`` +----------- + +Windows-only. When ``True``, uses the roaming AppData directory (``CSIDL_APPDATA``) instead of +the local one (``CSIDL_LOCAL_APPDATA``). Roaming profiles sync across machines in a Windows domain. + +.. code-block:: pycon + + >>> user_data_dir("SuperApp", "Acme", roaming=True) # on Windows + 'C:\\Users\\trentm\\AppData\\Roaming\\Acme\\SuperApp' + +On non-Windows platforms, this parameter is ignored. + +**Type**: ``bool`` + +**Default**: ``False`` + +**Platform**: Windows only + +``multipath`` +------------- + +Unix/macOS only. When ``True``, ``site_data_dir`` and ``site_config_dir`` return all directories +from ``XDG_DATA_DIRS`` / ``XDG_CONFIG_DIRS`` joined by ``os.pathsep`` (``:``) instead of just +the first one: + +.. code-block:: pycon + + >>> from platformdirs import site_data_dir + >>> site_data_dir("SuperApp", multipath=True) # on Linux + '/usr/local/share/SuperApp:/usr/share/SuperApp' + +**Type**: ``bool`` + +**Default**: ``False`` + +**Platform**: Unix/macOS only + +``opinion`` +----------- + +When ``True`` (the default), certain directories get an opinionated subdirectory. For example, on +Windows the cache directory includes a ``Cache`` subdirectory, and the log directory includes ``Logs``. +On Linux, the log directory appends ``/log``. Set to ``False`` to suppress this: + +.. code-block:: pycon + + >>> user_cache_dir("SuperApp", "Acme") # on Windows, opinion=True + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Cache' + >>> user_cache_dir("SuperApp", "Acme", opinion=False) # on Windows + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' + +**Type**: ``bool`` + +**Default**: ``True`` + +``ensure_exists`` +----------------- + +When ``True``, the directory is created (including parents) when the property is accessed. +Defaults to ``False``. + +.. code-block:: python + + from platformdirs import PlatformDirs + + dirs = PlatformDirs("SuperApp", "Acme", ensure_exists=True) + dirs.user_cache_dir # directory is created if it does not exist + +**Type**: ``bool`` + +**Default**: ``False`` + +**See also**: :ref:`creating-directories-safely` for manual directory creation patterns. + +``use_site_for_root`` +--------------------- + +Unix-only. When ``True``, redirects ``user_*_dir`` calls to their ``site_*_dir`` equivalents when +running as root (uid 0). Defaults to ``False`` for backwards compatibility. + +When enabled, XDG user environment variables (e.g., ``XDG_DATA_HOME``) are bypassed for the +redirected directories. This is useful for system services running as root that should use +system-wide directories rather than root's home directory. + +.. code-block:: python + + from platformdirs import PlatformDirs + + dirs = PlatformDirs("SuperApp", use_site_for_root=True) + # When running as root, user_data_dir returns the site_data_dir path + dirs.user_data_dir # Returns site directory instead of /root/.local/share/SuperApp + +**Type**: ``bool`` + +**Default**: ``False`` + +**Platform**: Unix only + +XDG environment variables +-------------------------- + +On Linux, ``platformdirs`` follows the `XDG Base Directory Specification +`_. Environment variables like +``XDG_DATA_HOME``, ``XDG_CONFIG_HOME``, ``XDG_CACHE_HOME``, and ``XDG_STATE_HOME`` override +the default directories when set. + +On macOS, the same XDG environment variables are also supported and take precedence over the +default macOS directories: + +.. code-block:: pycon + + >>> import os + >>> os.environ["XDG_CONFIG_HOME"] = "/Users/trentm/.config" + >>> user_config_dir("SuperApp") + '/Users/trentm/.config/SuperApp' + +Supported XDG variables: + +- ``XDG_DATA_HOME`` - user data directory. +- ``XDG_CONFIG_HOME`` - user config directory. +- ``XDG_CACHE_HOME`` - user cache directory. +- ``XDG_STATE_HOME`` - user state directory. +- ``XDG_DATA_DIRS`` - system data directories (colon-separated). +- ``XDG_CONFIG_DIRS`` - system config directories (colon-separated). +- ``XDG_RUNTIME_DIR`` - user runtime directory. + +Directories not covered +------------------------ + +``platformdirs`` does not provide a property for the user's **home directory**. Use +:meth:`pathlib.Path.home` or :func:`os.path.expanduser` from the standard library instead: + +.. code-block:: pycon + + >>> from pathlib import Path + >>> Path.home() + PosixPath('/Users/trentm') diff --git a/docs/platforms.rst b/docs/platforms.rst index 57c9a44..2119216 100644 --- a/docs/platforms.rst +++ b/docs/platforms.rst @@ -361,7 +361,7 @@ Default paths This property does not append ``appname`` or ``version``. It returns the directory where system-wide executables and scripts are placed. On Unix/Linux, this follows the `FHS 3.0 `_ - standard for locally-installed software. On Windows, it mirrors the ``site_data_dir`` + standard for locally installed software. On Windows, it mirrors the ``site_data_dir`` pattern using ``%ProgramData%``, following the precedent set by `Chocolatey `_. diff --git a/docs/usage.rst b/docs/usage.rst index e87fbb8..7328133 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,6 +1,9 @@ Usage guide =========== +This guide shows you how to use ``platformdirs`` to find the correct directories for your +application's data, config, cache, logs, and state on any platform. + Getting started --------------- @@ -24,8 +27,8 @@ Each function returns a :class:`str`. For a :class:`~pathlib.Path`, use the ``_p >>> user_data_path("SuperApp", "Acme") PosixPath('/Users/trentm/Library/Application Support/SuperApp') -Using the ``PlatformDirs`` class --------------------------------- +Using the PlatformDirs class +----------------------------- When you need multiple directories for the same application, instantiate :data:`~platformdirs.PlatformDirs` once and access its properties: @@ -48,126 +51,43 @@ Each :class:`str` property has a corresponding ``_path`` property that returns a >>> dirs.user_data_path PosixPath('/Users/trentm/Library/Application Support/SuperApp') -Parameters ----------- - -``appname`` -~~~~~~~~~~~ - -The name of your application. Used as a subdirectory in all app-specific paths. -When ``None``, the base platform directory is returned without any app-specific subdirectory. - -.. code-block:: pycon - - >>> user_data_dir("SuperApp") - '/Users/trentm/Library/Application Support/SuperApp' - >>> user_data_dir() - '/Users/trentm/Library/Application Support' - -``appauthor`` -~~~~~~~~~~~~~ - -The app author or distributing organization. On Windows, this adds an additional parent directory: - -.. code-block:: pycon - - >>> user_data_dir("SuperApp", "Acme") # on Windows - 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' - -Set to ``False`` to suppress the author directory even on Windows: - -.. code-block:: pycon - - >>> user_data_dir("SuperApp", False) # on Windows - 'C:\\Users\\trentm\\AppData\\Local\\SuperApp' - -On non-Windows platforms, ``appauthor`` is ignored. - -``version`` -~~~~~~~~~~~ - -Appends a version subdirectory. Useful for running multiple app versions side by side: - -.. code-block:: pycon - - >>> from platformdirs import PlatformDirs - >>> dirs = PlatformDirs("SuperApp", "Acme", version="1.0") - >>> dirs.user_data_dir - '/Users/trentm/Library/Application Support/SuperApp/1.0' - >>> dirs.user_cache_dir - '/Users/trentm/Library/Caches/SuperApp/1.0' - -Be wary of using this for configuration files; you will need to handle migrating configuration -files between versions manually. - -``roaming`` -~~~~~~~~~~~ - -Windows-only. When ``True``, uses the roaming AppData directory (``CSIDL_APPDATA``) instead of -the local one (``CSIDL_LOCAL_APPDATA``). Roaming profiles sync across machines in a Windows domain. - -.. code-block:: pycon - - >>> user_data_dir("SuperApp", "Acme", roaming=True) # on Windows - 'C:\\Users\\trentm\\AppData\\Roaming\\Acme\\SuperApp' - -``multipath`` -~~~~~~~~~~~~~ - -Unix/macOS only. When ``True``, ``site_data_dir`` and ``site_config_dir`` return all directories -from ``XDG_DATA_DIRS`` / ``XDG_CONFIG_DIRS`` joined by ``os.pathsep`` (``:``) instead of just -the first one: - -.. code-block:: pycon - - >>> from platformdirs import site_data_dir - >>> site_data_dir("SuperApp", multipath=True) # on Linux - '/usr/local/share/SuperApp:/usr/share/SuperApp' - -``opinion`` -~~~~~~~~~~~ - -When ``True`` (the default), certain directories get an opinionated subdirectory. For example, on -Windows the cache directory includes a ``Cache`` subdirectory, and the log directory includes ``Logs``. -On Linux, the log directory appends ``/log``. Set to ``False`` to suppress this: - -.. code-block:: pycon - - >>> user_cache_dir("SuperApp", "Acme") # on Windows, opinion=True - 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Cache' - >>> user_cache_dir("SuperApp", "Acme", opinion=False) # on Windows - 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' - -``ensure_exists`` -~~~~~~~~~~~~~~~~~ +Complete application example +----------------------------- -When ``True``, the directory is created (including parents) when the property is accessed. -Defaults to ``False``. +Here's a typical application setup using multiple directory types: .. code-block:: python from platformdirs import PlatformDirs + from pathlib import Path + import json + import logging - dirs = PlatformDirs("SuperApp", "Acme", ensure_exists=True) - dirs.user_cache_dir # directory is created if it does not exist + class MyApp: + def __init__(self): + # Initialize all directories at once + dirs = PlatformDirs("MyApp", "AcmeCompany", ensure_exists=True) -``use_site_for_root`` -~~~~~~~~~~~~~~~~~~~~~ + # Persistent data (database, downloads) + self.db_path = dirs.user_data_path / "app.db" -Unix-only. When ``True``, redirects ``user_*_dir`` calls to their ``site_*_dir`` equivalents when -running as root (uid 0). Defaults to ``False`` for backwards compatibility. + # User configuration + self.config_path = dirs.user_config_path / "settings.json" -When enabled, XDG user environment variables (e.g., ``XDG_DATA_HOME``) are bypassed for the -redirected directories. This is useful for system services running as root that should use -system-wide directories rather than root's home directory. + # Cache for performance + self.cache_dir = dirs.user_cache_path -.. code-block:: python + # Application logs + log_file = dirs.user_log_path / "app.log" + logging.basicConfig(filename=log_file, level=logging.INFO) - from platformdirs import PlatformDirs + # Window state and UI preferences + self.state_path = dirs.user_state_path / "window.json" - dirs = PlatformDirs("SuperApp", use_site_for_root=True) - # When running as root, user_data_dir returns the site_data_dir path - dirs.user_data_dir # Returns site directory instead of /root/.local/share/SuperApp + def load_config(self): + if self.config_path.exists(): + return json.loads(self.config_path.read_text()) + return {"theme": "light", "autosave": True} Choosing the right directory ----------------------------- @@ -181,14 +101,13 @@ Data directories Use ``user_data_dir`` and ``site_data_dir`` for persistent application data that the user expects to keep: -- SQLite databases, document stores -- Downloaded files, media assets -- User-created content -- Application state that must survive app updates +- SQLite databases, document stores. +- Downloaded files, media assets. +- User-created content. +- Application state that must survive app updates. .. code-block:: python - from pathlib import Path from platformdirs import user_data_path db_path = user_data_path("MyApp") / "app.db" @@ -199,10 +118,10 @@ Config directories Use ``user_config_dir`` and ``site_config_dir`` for configuration files and user preferences: -- Settings files (JSON, TOML, INI, YAML) -- User preferences and options -- Application themes, keybindings -- Feature flags and toggles +- Settings files (JSON, TOML, INI, YAML). +- User preferences and options. +- Application themes, keybindings. +- Feature flags and toggles. .. code-block:: python @@ -220,10 +139,10 @@ Cache directories Use ``user_cache_dir`` and ``site_cache_dir`` for regenerable data that improves performance: -- API response caches -- Thumbnail images, processed media -- Compiled templates, bytecode -- Downloaded package indexes +- API response caches. +- Thumbnail images, processed media. +- Compiled templates, bytecode. +- Downloaded package indexes. Cached data can be safely deleted without losing functionality. Applications should gracefully handle missing cache directories. @@ -241,10 +160,10 @@ State directories Use ``user_state_dir`` and ``site_state_dir`` for non-critical runtime state: -- Window positions, sizes -- Recently opened files, MRU lists -- Undo/redo history -- Search history, autocomplete data +- Window positions, sizes. +- Recently opened files, MRU lists. +- Undo/redo history. +- Search history, autocomplete data. State persists between sessions but is less important than data or config. Loss of state is inconvenient but not catastrophic. @@ -264,10 +183,10 @@ Log directories Use ``user_log_dir`` and ``site_log_dir`` for application logs: -- Debug logs, error logs -- Audit trails, access logs -- Performance metrics -- Crash reports +- Debug logs, error logs. +- Audit trails, access logs. +- Performance metrics. +- Crash reports. .. code-block:: python @@ -293,10 +212,10 @@ User directories User directories (``user_data_dir``, ``user_config_dir``, etc.) are: -- **Per-user**: Each user on the system has their own separate directory -- **Writable**: Normal users can read and write without special permissions -- **Isolated**: Changes by one user don't affect others -- **Default choice**: Use these unless you specifically need system-wide access +- **Per-user**: Each user on the system has their own separate directory. +- **Writable**: Normal users can read and write without special permissions. +- **Isolated**: Changes by one user don't affect others. +- **Default choice**: Use these unless you specifically need system-wide access. .. code-block:: python @@ -310,15 +229,14 @@ Site directories Site directories (``site_data_dir``, ``site_config_dir``, etc.) are: -- **System-wide**: Shared across all users on the machine -- **Read-only for users**: Typically require administrator/root privileges to write -- **System defaults**: Store default configurations, shared resources -- **Package managers**: Used by system package managers for application data +- **System-wide**: Shared across all users on the machine. +- **Read-only for users**: Typically require administrator/root privileges to write. +- **System defaults**: Store default configurations, shared resources. +- **Package managers**: Used by system package managers for application data. .. code-block:: python from platformdirs import site_config_path, user_config_path - from pathlib import Path # Check site config first (system defaults), then user config (overrides) site_cfg = site_config_path("MyApp") / "defaults.json" @@ -331,125 +249,6 @@ Site directories (``site_data_dir``, ``site_config_dir``, etc.) are: else: config = None -Common patterns ---------------- - -Creating directories safely -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Always create parent directories before writing files: - -.. code-block:: python - - 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: - -.. code-block:: python - - 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 - -Handling write errors -~~~~~~~~~~~~~~~~~~~~~ - -Directory paths may not be writable due to permissions or disk space: - -.. code-block:: python - - from platformdirs import user_data_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: - -.. code-block:: python - - 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: - -.. code-block:: python - - 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: - -.. code-block:: python - - 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 - current_dir.mkdir(parents=True, exist_ok=True) - for item in previous_dir.iterdir(): - shutil.copy2(item, current_dir / item.name) - Real-world examples ------------------- @@ -558,104 +357,10 @@ tox (testing tool) See `tox/config/cli/ini.py `_ for the full implementation. -Platform-specific considerations ---------------------------------- - -Windows -~~~~~~~ - -**Store Python sandbox**: Applications installed via the Microsoft Store run in a restricted -sandbox environment. ``platformdirs`` handles this automatically, but be aware that: - -- File system access is limited to specific directories -- Some APIs may return sandbox-specific paths -- Network access may require additional permissions - -**Roaming vs local**: Use ``roaming=True`` for settings that should sync across domain-joined -machines. Use local (default) for machine-specific data like caches. - -.. code-block:: python - - from platformdirs import user_config_path - - # Synced across domain computers - roaming_cfg = user_config_path("MyApp", roaming=True) - - # Local to this machine - local_cache = user_cache_path("MyApp") - -macOS -~~~~~ - -**Data and config location**: On macOS, ``user_data_dir`` and ``user_config_dir`` both return -``~/Library/Application Support/AppName`` by default. Use subdirectories to separate concerns: - -.. code-block:: python - - from platformdirs import user_data_path - - app_dir = user_data_path("MyApp") - config_dir = app_dir / "config" - databases_dir = app_dir / "databases" - -**XDG support**: macOS now supports XDG environment variables. If ``XDG_DATA_HOME`` is set, -``platformdirs`` will honor it instead of using ``~/Library``. - -Linux/Unix -~~~~~~~~~~ - -**Permissions**: Writing to ``site_*`` directories typically requires root privileges. Normal -users can only read from these locations. - -**XDG environment variables**: ``platformdirs`` fully supports the XDG Base Directory Specification. -Users can override default paths by setting environment variables like ``XDG_DATA_HOME``, -``XDG_CONFIG_HOME``, etc. - -**Running as root**: When running as root (uid 0), ``user_*`` directories default to root's home -directory (``/root``). Use ``use_site_for_root=True`` to redirect to system directories instead: - -.. code-block:: python - - from platformdirs import PlatformDirs - - # System service that should use /var/lib instead of /root - dirs = PlatformDirs("myservice", use_site_for_root=True) - -Android -~~~~~~~ - -**App-specific paths**: All directories are within your app's private storage -(``/data/data//``). Data is automatically removed when the app is uninstalled. - -**External storage**: ``platformdirs`` does not provide paths for external/shared storage -(SD cards, ``/storage/emulated/0/``). Use Android-specific APIs for shared storage. - -Directories not covered ------------------------ - -``platformdirs`` does not provide a property for the user's **home directory**. Use -:meth:`pathlib.Path.home` or :func:`os.path.expanduser` from the standard library instead: - -.. code-block:: pycon - - >>> from pathlib import Path - >>> Path.home() - PosixPath('/Users/trentm') - -XDG environment variables -------------------------- - -On Linux, ``platformdirs`` follows the `XDG Base Directory Specification -`_. Environment variables like -``XDG_DATA_HOME``, ``XDG_CONFIG_HOME``, ``XDG_CACHE_HOME``, and ``XDG_STATE_HOME`` override -the default directories when set. - -On macOS, the same XDG environment variables are also supported and take precedence over the -default macOS directories: - -.. code-block:: pycon +Next steps +---------- - >>> import os - >>> os.environ["XDG_CONFIG_HOME"] = "/Users/trentm/.config" - >>> user_config_dir("SuperApp") - '/Users/trentm/.config/SuperApp' +- See :doc:`parameters` for detailed parameter reference. +- See :doc:`howto` for common patterns and platform-specific guidance. +- See :doc:`api` for complete API documentation. +- See :doc:`platforms` for platform-specific path details. diff --git a/tox.toml b/tox.toml index 65c1fe8..5259026 100644 --- a/tox.toml +++ b/tox.toml @@ -98,7 +98,7 @@ commands = [ [ "python", "-c", - "import glob; import subprocess; subprocess.call(['proselint', 'check'] + glob.glob('docs/*.rst'))", + "import glob; import subprocess; subprocess.check_call(['proselint', 'check'] + glob.glob('docs/*.rst'))", ], [ "sphinx-build",