diff --git a/README.md b/README.md index 7cd5d88e..e2627bd7 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,26 @@ user_config_path("MyApp", "MyCompany") # returns pathlib.Path ## Directory types +**Application directories** — scoped to your app name and version: + - **Data**: Persistent application data (`user_data_dir`, `site_data_dir`) - **Config**: Configuration files and settings (`user_config_dir`, `site_config_dir`) +- **Preference**: User preferences, distinct from config on macOS (`user_preference_dir`) - **Cache**: Cached data that can be regenerated (`user_cache_dir`, `site_cache_dir`) - **State**: Non-essential runtime state like window positions (`user_state_dir`, `site_state_dir`) - **Logs**: Log files (`user_log_dir`, `site_log_dir`) - **Runtime**: Runtime files like sockets and PIDs (`user_runtime_dir`, `site_runtime_dir`) -Each type has both `user_*` (per-user, writable) and `site_*` (system-wide, read-only for users) variants. +App dirs have both `user_*` (per-user, writable) and `site_*` (system-wide, read-only) variants where applicable. + +**User media directories** — standard user-facing folders, not scoped to app name: + +- **Documents** (`user_documents_dir`), **Downloads** (`user_downloads_dir`) +- **Pictures** (`user_pictures_dir`), **Videos** (`user_videos_dir`), **Music** (`user_music_dir`) +- **Desktop** (`user_desktop_dir`), **Projects** (`user_projects_dir`) +- **Public share** (`user_publicshare_dir`), **Templates** (`user_templates_dir`) +- **Fonts** (`user_fonts_dir`) — user-writable font installation directory +- **Executable** (`user_bin_dir`, `site_bin_dir`), **Applications** (`user_applications_dir`, `site_applications_dir`) ## Documentation diff --git a/docs/api.rst b/docs/api.rst index 927d11a1..9c298768 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -150,6 +150,42 @@ See also: :ref:`platforms:``user_projects_dir``` .. autofunction:: platformdirs.user_projects_path +User public share directory +=========================== + +See also: :ref:`platforms:``user_publicshare_dir``` + +.. autofunction:: platformdirs.user_publicshare_dir + +.. autofunction:: platformdirs.user_publicshare_path + +User templates directory +======================== + +See also: :ref:`platforms:``user_templates_dir``` + +.. autofunction:: platformdirs.user_templates_dir + +.. autofunction:: platformdirs.user_templates_path + +User fonts directory +==================== + +See also: :ref:`platforms:``user_fonts_dir``` + +.. autofunction:: platformdirs.user_fonts_dir + +.. autofunction:: platformdirs.user_fonts_path + +User preference directory +========================= + +See also: :ref:`platforms:``user_preference_dir``` + +.. autofunction:: platformdirs.user_preference_dir + +.. autofunction:: platformdirs.user_preference_path + ******************** Shared directories ******************** diff --git a/docs/explanation.rst b/docs/explanation.rst index 36f98c7e..ccdc09db 100644 --- a/docs/explanation.rst +++ b/docs/explanation.rst @@ -17,23 +17,74 @@ purpose. Application authors write platform-agnostic code while end users get pa Choosing the right directory ****************************** -``platformdirs`` provides different directory types for different kinds of data. Choose based on the data's purpose and -lifetime. +The first question is always: who owns this data? **App-internal data** — databases, caches, config files, logs — goes +in an app dir scoped to your app name. **User-facing data** — files the user would browse to directly — goes in a media +dir that sits alongside their documents, music, and photos. + +Within app dirs, the next question is whether the data is essential. If it can be regenerated, use ``cache`` (fast +lookups) or ``runtime`` (session-only sockets and PIDs). If it is important but not critical, use ``state`` (window +positions, recent files). For settings use ``config``; on macOS, ``preference`` gives you the separate +``~/Library/Preferences`` location that Apple convention expects. Use ``data`` for everything else that must survive app +updates. + +Within media dirs, pick the folder that matches the file's type from the user's perspective — not what your app does +with it. A font your app installs for the user goes in ``fonts``, not ``data``. .. mermaid:: flowchart TD - A[What kind of data?] --> B{Can it be deleted
without data loss?} - B -- Yes --> C{Is it used to
speed things up?} - C -- Yes --> D[**cache** dir] - C -- No --> E{Is it temporary
for this session?} - E -- Yes --> F[**runtime** dir] - E -- No --> G[**state** dir] - B -- No --> H{Is it a
user preference?} - H -- Yes --> I[**config** dir] - H -- No --> J{Is it a
log file?} - J -- Yes --> K[**log** dir] - J -- No --> L[**data** dir] + A([What kind of data?]) --> B{Belongs to the app
or to the user?} + + B -- App internal --> C{Can it be deleted
without data loss?} + C -- Yes --> D{Speeds things up?} + D -- Yes --> CACHE[cache dir] + D -- No --> E{Temporary for
this session only?} + E -- Yes --> RUNTIME[runtime dir] + E -- No --> STATE[state dir] + C -- No --> F{What kind?} + F -- Settings / options --> CONFIG[config dir] + F -- macOS preferences --> PREF[preference dir] + F -- Log file --> LOG[log dir] + F -- Everything else --> DATA[data dir] + + B -- User-facing file --> G{File type?} + G -- Document / report --> DOC[documents dir] + G -- Downloaded content --> DL[downloads dir] + G -- Image --> PIC[pictures dir] + G -- Video --> VID[videos dir] + G -- Audio --> MUS[music dir] + G -- Font --> FONT[fonts dir] + G -- Template --> TMPL[templates dir] + G -- Project / code --> PROJ[projects dir] + G -- Desktop shortcut --> DESK[desktop dir] + G -- Share with others --> PUB[publicshare dir] + + style A fill:#1e40af,stroke:#1e3a8a,color:#fff + style B fill:#d97706,stroke:#b45309,color:#fff + style C fill:#d97706,stroke:#b45309,color:#fff + style D fill:#d97706,stroke:#b45309,color:#fff + style E fill:#d97706,stroke:#b45309,color:#fff + style F fill:#d97706,stroke:#b45309,color:#fff + style G fill:#d97706,stroke:#b45309,color:#fff + + style CACHE fill:#2563eb,stroke:#1d4ed8,color:#fff + style RUNTIME fill:#2563eb,stroke:#1d4ed8,color:#fff + style STATE fill:#2563eb,stroke:#1d4ed8,color:#fff + style CONFIG fill:#2563eb,stroke:#1d4ed8,color:#fff + style PREF fill:#7c3aed,stroke:#6d28d9,color:#fff + style LOG fill:#2563eb,stroke:#1d4ed8,color:#fff + style DATA fill:#2563eb,stroke:#1d4ed8,color:#fff + + style DOC fill:#16a34a,stroke:#15803d,color:#fff + style DL fill:#16a34a,stroke:#15803d,color:#fff + style PIC fill:#16a34a,stroke:#15803d,color:#fff + style VID fill:#16a34a,stroke:#15803d,color:#fff + style MUS fill:#16a34a,stroke:#15803d,color:#fff + style FONT fill:#16a34a,stroke:#15803d,color:#fff + style TMPL fill:#16a34a,stroke:#15803d,color:#fff + style PROJ fill:#16a34a,stroke:#15803d,color:#fff + style DESK fill:#16a34a,stroke:#15803d,color:#fff + style PUB fill:#16a34a,stroke:#15803d,color:#fff Data directories ================ @@ -141,6 +192,102 @@ Use ``user_log_dir`` and ``site_log_dir`` for application logs: format="%(asctime)s - %(levelname)s - %(message)s", ) +************************ + User media directories +************************ + +Unlike app dirs (data, config, cache, etc.), media dirs are **not** scoped to the app name. They point to standard +user-facing folders that exist independently of any particular application. Use them when your app needs to read from or +save into a folder the user already expects — not when storing application state. + +The distinction matters: + +- ``user_data_dir("MyApp")`` → ``~/.local/share/MyApp`` — your app's private storage +- ``user_documents_dir()`` → ``~/Documents`` — the user's document library + +On Linux, media dirs are defined by the `XDG user-dirs specification +`_ and stored in ``~/.config/user-dirs.dirs``. The +``xdg-user-dirs`` tool lets users relocate them. Set the corresponding environment variable (``XDG_DOCUMENTS_DIR``, +``XDG_DOWNLOAD_DIR``, etc.) to override on a per-session basis. On macOS and Windows, ``platformdirs`` returns the +platform-conventional location. + +Media and user-facing directories +================================= + +Use these when your app saves or opens files the user should see in their own folders: + +``user_documents_dir`` (``XDG_DOCUMENTS_DIR``) + Exported reports, user-authored files. Save here when the file is *for the user*, not the app. + +``user_downloads_dir`` (``XDG_DOWNLOAD_DIR``) + Files fetched from the internet at the user's request. + +``user_pictures_dir`` / ``user_videos_dir`` / ``user_music_dir`` + Platform media libraries. Use when importing or exporting to the user's existing collection. + +``user_desktop_dir`` (``XDG_DESKTOP_DIR``) + Shortcut files and launchers. Rarely needed directly in code. + +``user_projects_dir`` (``XDG_PROJECTS_DIR``) + Root directory for the user's coding projects. `Recently added to xdg-user-dirs + `_. + +``user_publicshare_dir`` (``XDG_PUBLICSHARE_DIR``) + Files shared with other local accounts. On Windows this is the machine-wide ``C:\Users\Public`` (``%PUBLIC%``), not + a per-user directory. + +.. code-block:: python + + from platformdirs import user_documents_path + + report = user_documents_path() / "report.pdf" + +Do not use these to store application state or config — if the file would confuse the user when they browse the folder, +it belongs in ``user_data_dir`` instead. + +Templates +========= + +``user_templates_dir`` (``XDG_TEMPLATES_DIR``) points to the folder used by file managers for new-file templates. macOS +has no platform-defined templates directory; ``~/Templates`` is returned as a pragmatic fallback. + +Fonts +===== + +``user_fonts_dir`` points to the per-user font installation directory: + +- **Linux**: ``$XDG_DATA_HOME/fonts`` (default ``~/.local/share/fonts``) — derived from ``$XDG_DATA_HOME``, not a + dedicated env var. See the `XDG Base Directory Specification + `_. +- **macOS**: ``~/Library/Fonts`` +- **Windows**: ``%LOCALAPPDATA%\Microsoft\Windows\Fonts`` — the per-user font location added in Windows 10 + +.. code-block:: python + + import shutil + from platformdirs import user_fonts_path + + font_dir = user_fonts_path() + font_dir.mkdir(parents=True, exist_ok=True) + shutil.copy("MyFont.ttf", font_dir / "MyFont.ttf") + +********************** + Preference directory +********************** + +``user_preference_dir`` is meaningful mainly on macOS, where Apple's conventions distinguish two separate locations: + +- ``~/Library/Application Support/AppName`` — long-term application data, databases, plug-ins +- ``~/Library/Preferences/AppName`` — user-adjustable preference files (historically ``.plist``) + +On Linux and Windows, ``user_preference_dir`` is an alias for ``user_config_dir`` — the XDG and Windows conventions make +no such distinction. On Android, it also aliases ``user_config_dir``. + +Use ``user_preference_dir`` when you specifically need to follow Apple's `File System Programming Guide +`_ +and store preference files in ``~/Library/Preferences``. For most cross-platform applications ``user_config_dir`` is +sufficient. + ************************** User vs site directories ************************** diff --git a/docs/index.rst b/docs/index.rst index a894f4d6..e9540b33 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,15 +44,30 @@ and Android. Keep multiple app versions side by side with the ``version`` parameter. .. toctree:: - :maxdepth: 2 - :caption: Contents + :hidden: + :caption: Tutorial tutorial + +.. toctree:: + :hidden: + :caption: How-to guides + howto - explanation + +.. toctree:: + :hidden: + :caption: Reference + parameters api platforms + +.. toctree:: + :hidden: + :caption: Explanation + + explanation changelog ******************** diff --git a/docs/platforms.rst b/docs/platforms.rst index 9f347123..94a2c039 100644 --- a/docs/platforms.rst +++ b/docs/platforms.rst @@ -406,6 +406,10 @@ See also: :ref:`api:User desktop directory` See also: :ref:`api:User projects directory` +Defined by `$XDG_PROJECTS_DIR +`_ (recently added +to xdg-user-dirs). + .. tab-set:: .. tab-item:: Linux @@ -429,6 +433,134 @@ See also: :ref:`api:User projects directory` ``/storage/emulated/0/Projects`` +``user_publicshare_dir`` +======================== + +See also: :ref:`api:User public share directory` + +Defined by `$XDG_PUBLICSHARE_DIR `_. + +On Windows, this is the machine-wide ``C:\Users\Public`` (``%PUBLIC%``), shared across all local accounts — not a +per-user directory. See `FOLDERID_Public `_. + +.. tab-set:: + + .. tab-item:: Linux + :sync: linux + + ``~/Public`` (from ``$XDG_PUBLICSHARE_DIR`` if set, else ``$XDG_PUBLICSHARE_DIR`` entry in + ``user-dirs.dirs``, else ``~/Public``) + + .. tab-item:: macOS + :sync: macos + + ``~/Public`` + + .. tab-item:: Windows + :sync: windows + + ``C:\Users\Public`` (``%PUBLIC%``) + + .. tab-item:: Android + :sync: android + + ``/storage/emulated/0/Public`` + +``user_templates_dir`` +====================== + +See also: :ref:`api:User templates directory` + +Defined by `$XDG_TEMPLATES_DIR `_. macOS has no +platform-defined templates directory; ``~/Templates`` is returned as a pragmatic fallback. On Windows, see +`FOLDERID_Templates `_. + +.. tab-set:: + + .. tab-item:: Linux + :sync: linux + + ``~/Templates`` (from ``$XDG_TEMPLATES_DIR`` if set, else ``$XDG_TEMPLATES_DIR`` entry in + ``user-dirs.dirs``, else ``~/Templates``) + + .. tab-item:: macOS + :sync: macos + + ``~/Templates`` (pragmatic fallback; macOS has no native templates directory) + + .. tab-item:: Windows + :sync: windows + + ``%APPDATA%\Microsoft\Windows\Templates`` + + .. tab-item:: Android + :sync: android + + ``/storage/emulated/0/Templates`` + +``user_fonts_dir`` +================== + +See also: :ref:`api:User fonts directory` + +Derived from ``$XDG_DATA_HOME/fonts`` on Linux (no dedicated env var). See the `XDG Base Directory Specification +`_. On Windows, uses the per-user font location added in Windows +10. + +.. tab-set:: + + .. tab-item:: Linux + :sync: linux + + ``$XDG_DATA_HOME/fonts`` (default ``~/.local/share/fonts``) + + .. tab-item:: macOS + :sync: macos + + ``~/Library/Fonts`` + + .. tab-item:: Windows + :sync: windows + + ``%LOCALAPPDATA%\Microsoft\Windows\Fonts`` + + .. tab-item:: Android + :sync: android + + ``/storage/emulated/0/fonts`` + +``user_preference_dir`` +======================= + +See also: :ref:`api:User preference directory` + +On macOS, ``~/Library/Preferences`` is distinct from ``~/Library/Application Support`` (``user_config_dir``). See +`Apple's File System Programming Guide +`_. +On all other platforms, this aliases ``user_config_dir``. + +.. tab-set:: + + .. tab-item:: Linux + :sync: linux + + Same as ``user_config_dir`` (``$XDG_CONFIG_HOME`` or ``~/.config/AppName``) + + .. tab-item:: macOS + :sync: macos + + ``~/Library/Preferences/AppName`` (distinct from ``~/Library/Application Support``) + + .. tab-item:: Windows + :sync: windows + + Same as ``user_config_dir`` (``%APPDATA%\AppName``) + + .. tab-item:: Android + :sync: android + + Same as ``user_config_dir`` + ******************** Shared directories ******************** diff --git a/src/platformdirs/__init__.py b/src/platformdirs/__init__.py index d0867d5f..e9d3cb67 100644 --- a/src/platformdirs/__init__.py +++ b/src/platformdirs/__init__.py @@ -349,6 +349,26 @@ def user_projects_dir() -> str: return PlatformDirs().user_projects_dir +def user_publicshare_dir() -> str: + """:returns: public share directory tied to the user""" + return PlatformDirs().user_publicshare_dir + + +def user_templates_dir() -> str: + """:returns: templates directory tied to the user""" + return PlatformDirs().user_templates_dir + + +def user_fonts_dir() -> str: + """:returns: fonts directory tied to the user""" + return PlatformDirs().user_fonts_dir + + +def user_preference_dir() -> str: + """:returns: preference directory tied to the user""" + return PlatformDirs().user_preference_dir + + def user_bin_dir() -> str: """:returns: bin directory tied to the user""" return PlatformDirs().user_bin_dir @@ -730,6 +750,26 @@ def user_projects_path() -> Path: return PlatformDirs().user_projects_path +def user_publicshare_path() -> Path: + """:returns: public share path tied to the user""" + return PlatformDirs().user_publicshare_path + + +def user_templates_path() -> Path: + """:returns: templates path tied to the user""" + return PlatformDirs().user_templates_path + + +def user_fonts_path() -> Path: + """:returns: fonts path tied to the user""" + return PlatformDirs().user_fonts_path + + +def user_preference_path() -> Path: + """:returns: preference path tied to the user""" + return PlatformDirs().user_preference_path + + def user_bin_path() -> Path: """:returns: bin path tied to the user""" return PlatformDirs().user_bin_path @@ -852,18 +892,26 @@ def site_runtime_path( "user_documents_path", "user_downloads_dir", "user_downloads_path", + "user_fonts_dir", + "user_fonts_path", "user_log_dir", "user_log_path", "user_music_dir", "user_music_path", "user_pictures_dir", "user_pictures_path", + "user_preference_dir", + "user_preference_path", "user_projects_dir", "user_projects_path", + "user_publicshare_dir", + "user_publicshare_path", "user_runtime_dir", "user_runtime_path", "user_state_dir", "user_state_path", + "user_templates_dir", + "user_templates_path", "user_videos_dir", "user_videos_path", ] diff --git a/src/platformdirs/__main__.py b/src/platformdirs/__main__.py index 11d1e8eb..7a52b7d4 100644 --- a/src/platformdirs/__main__.py +++ b/src/platformdirs/__main__.py @@ -16,6 +16,10 @@ "user_videos_dir", "user_music_dir", "user_projects_dir", + "user_publicshare_dir", + "user_templates_dir", + "user_fonts_dir", + "user_preference_dir", "user_bin_dir", "site_bin_dir", "user_applications_dir", diff --git a/src/platformdirs/_xdg.py b/src/platformdirs/_xdg.py index fae5750b..b2af284a 100644 --- a/src/platformdirs/_xdg.py +++ b/src/platformdirs/_xdg.py @@ -122,9 +122,30 @@ def user_desktop_dir(self) -> str: def user_projects_dir(self) -> str: """:returns: projects directory tied to the user, from ``$XDG_PROJECTS_DIR`` if set, else platform default""" if path := os.environ.get("XDG_PROJECTS_DIR", "").strip(): - return os.path.expanduser(path) # noqa: PTH111 + return os.path.expanduser(path) # noqa: PTH111 # API returns str, not Path return super().user_projects_dir + @property + def user_publicshare_dir(self) -> str: + """:returns: public share directory tied to the user, from ``$XDG_PUBLICSHARE_DIR`` if set, else platform default""" + if path := os.environ.get("XDG_PUBLICSHARE_DIR", "").strip(): + return os.path.expanduser(path) # noqa: PTH111 # API returns str, not Path + return super().user_publicshare_dir + + @property + def user_templates_dir(self) -> str: + """:returns: templates directory tied to the user, from ``$XDG_TEMPLATES_DIR`` if set, else platform default""" + if path := os.environ.get("XDG_TEMPLATES_DIR", "").strip(): + return os.path.expanduser(path) # noqa: PTH111 # API returns str, not Path + return super().user_templates_dir + + @property + def user_fonts_dir(self) -> str: + """:returns: fonts directory tied to the user, from ``$XDG_DATA_HOME/fonts`` if set, else platform default""" + if path := os.environ.get("XDG_DATA_HOME", "").strip(): + return f"{os.path.expanduser(path)}/fonts" # noqa: PTH111 # API returns str, not Path + return super().user_fonts_dir + @property def user_applications_dir(self) -> str: """:returns: applications directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default""" diff --git a/src/platformdirs/android.py b/src/platformdirs/android.py index 21c56b9c..885e8c6e 100644 --- a/src/platformdirs/android.py +++ b/src/platformdirs/android.py @@ -112,6 +112,26 @@ def user_projects_dir(self) -> str: """:returns: projects directory tied to the user e.g. ``/storage/emulated/0/Projects``""" return "/storage/emulated/0/Projects" + @property + def user_publicshare_dir(self) -> str: + """:returns: public share directory tied to the user e.g. ``/storage/emulated/0/Public``""" + return "/storage/emulated/0/Public" + + @property + def user_templates_dir(self) -> str: + """:returns: templates directory tied to the user e.g. ``/storage/emulated/0/Templates``""" + return "/storage/emulated/0/Templates" + + @property + def user_fonts_dir(self) -> str: + """:returns: fonts directory tied to the user e.g. ``/storage/emulated/0/fonts``""" + return "/storage/emulated/0/fonts" + + @property + def user_preference_dir(self) -> str: + """:returns: preference directory tied to the user, same as ``user_config_dir``""" + return self.user_config_dir + @property def user_bin_dir(self) -> str: """:returns: bin directory tied to the user, e.g. ``/data/user///files/bin``""" diff --git a/src/platformdirs/api.py b/src/platformdirs/api.py index 65a06b9f..91ef9028 100644 --- a/src/platformdirs/api.py +++ b/src/platformdirs/api.py @@ -213,6 +213,26 @@ def user_desktop_dir(self) -> str: def user_projects_dir(self) -> str: """:returns: projects directory tied to the user""" + @property + @abstractmethod + def user_publicshare_dir(self) -> str: + """:returns: public share directory tied to the user""" + + @property + @abstractmethod + def user_templates_dir(self) -> str: + """:returns: templates directory tied to the user""" + + @property + @abstractmethod + def user_fonts_dir(self) -> str: + """:returns: fonts directory tied to the user""" + + @property + @abstractmethod + def user_preference_dir(self) -> str: + """:returns: preference directory tied to the user""" + @property @abstractmethod def user_bin_dir(self) -> str: @@ -332,6 +352,26 @@ def user_projects_path(self) -> Path: """:returns: projects path tied to the user""" return Path(self.user_projects_dir) + @property + def user_publicshare_path(self) -> Path: + """:returns: public share path tied to the user""" + return Path(self.user_publicshare_dir) + + @property + def user_templates_path(self) -> Path: + """:returns: templates path tied to the user""" + return Path(self.user_templates_dir) + + @property + def user_fonts_path(self) -> Path: + """:returns: fonts path tied to the user""" + return Path(self.user_fonts_dir) + + @property + def user_preference_path(self) -> Path: + """:returns: preference path tied to the user""" + return Path(self.user_preference_dir) + @property def user_bin_path(self) -> Path: """:returns: bin path tied to the user""" diff --git a/src/platformdirs/macos.py b/src/platformdirs/macos.py index 59c62aa9..9ebc2a0c 100644 --- a/src/platformdirs/macos.py +++ b/src/platformdirs/macos.py @@ -139,6 +139,26 @@ def user_projects_dir(self) -> str: """:returns: projects directory tied to the user, e.g. ``~/Projects``""" return os.path.expanduser("~/Projects") # noqa: PTH111 + @property + def user_publicshare_dir(self) -> str: + """:returns: public share directory tied to the user, e.g. ``~/Public``""" + return os.path.expanduser("~/Public") # noqa: PTH111 # API returns str, not Path + + @property + def user_templates_dir(self) -> str: + """:returns: templates directory tied to the user, e.g. ``~/Templates``""" + return os.path.expanduser("~/Templates") # noqa: PTH111 # API returns str, not Path + + @property + def user_fonts_dir(self) -> str: + """:returns: fonts directory tied to the user, e.g. ``~/Library/Fonts``""" + return os.path.expanduser("~/Library/Fonts") # noqa: PTH111 # API returns str, not Path + + @property + def user_preference_dir(self) -> str: + """:returns: preference directory tied to the user, e.g. ``~/Library/Preferences/AppName``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Preferences")) # noqa: PTH111 # API returns str, not Path + @property def user_bin_dir(self) -> str: """:returns: bin directory tied to the user, e.g. ``~/.local/bin``""" diff --git a/src/platformdirs/unix.py b/src/platformdirs/unix.py index f9bc91b2..500d3a4b 100644 --- a/src/platformdirs/unix.py +++ b/src/platformdirs/unix.py @@ -128,6 +128,26 @@ def user_projects_dir(self) -> str: """:returns: projects directory tied to the user, e.g. ``~/Projects``""" return _get_user_media_dir("XDG_PROJECTS_DIR", "~/Projects") + @property + def user_publicshare_dir(self) -> str: + """:returns: public share directory tied to the user, e.g. ``~/Public``""" + return _get_user_media_dir("XDG_PUBLICSHARE_DIR", "~/Public") + + @property + def user_templates_dir(self) -> str: + """:returns: templates directory tied to the user, e.g. ``~/Templates``""" + return _get_user_media_dir("XDG_TEMPLATES_DIR", "~/Templates") + + @property + def user_fonts_dir(self) -> str: + """:returns: fonts directory tied to the user, e.g. ``~/.local/share/fonts``""" + return f"{os.path.expanduser('~/.local/share')}/fonts" # noqa: PTH111 # API returns str, not Path + + @property + def user_preference_dir(self) -> str: + """:returns: preference directory tied to the user, same as ``user_config_dir``""" + return self.user_config_dir + @property def user_bin_dir(self) -> str: """:returns: bin directory tied to the user, e.g. ``~/.local/bin``""" diff --git a/src/platformdirs/windows.py b/src/platformdirs/windows.py index 3d678ddb..476ffe73 100644 --- a/src/platformdirs/windows.py +++ b/src/platformdirs/windows.py @@ -4,6 +4,7 @@ import os import sys +from pathlib import Path from typing import TYPE_CHECKING, Final from .api import PlatformDirsABC @@ -138,6 +139,26 @@ def user_projects_dir(self) -> str: r""":returns: projects directory tied to the user, e.g. ``%USERPROFILE%\Projects``""" return os.path.normpath(os.path.expanduser("~/Projects")) # noqa: PTH111 + @property + def user_publicshare_dir(self) -> str: + r""":returns: public share directory e.g. ``C:\Users\Public``""" + return os.path.normpath(os.environ.get("PUBLIC", str(Path("~").expanduser().parent / "Public"))) + + @property + def user_templates_dir(self) -> str: + r""":returns: templates directory tied to the user e.g. ``%APPDATA%\Microsoft\Windows\Templates``""" + return os.path.normpath(str(Path(get_win_folder("CSIDL_APPDATA")) / "Microsoft" / "Windows" / "Templates")) + + @property + def user_fonts_dir(self) -> str: + r""":returns: fonts directory tied to the user e.g. ``%LOCALAPPDATA%\Microsoft\Windows\Fonts``""" + return os.path.normpath(str(Path(get_win_folder("CSIDL_LOCAL_APPDATA")) / "Microsoft" / "Windows" / "Fonts")) + + @property + def user_preference_dir(self) -> str: + r""":returns: preference directory tied to the user, same as ``user_config_dir``""" + return self.user_config_dir + @property def user_bin_dir(self) -> str: r""":returns: bin directory tied to the user, e.g. ``%LOCALAPPDATA%\Programs``""" diff --git a/tests/conftest.py b/tests/conftest.py index dc0833ad..a41bc89c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,10 @@ "user_videos_dir", "user_music_dir", "user_projects_dir", + "user_publicshare_dir", + "user_templates_dir", + "user_fonts_dir", + "user_preference_dir", "user_bin_dir", "site_bin_dir", "user_applications_dir", diff --git a/tests/test_android.py b/tests/test_android.py index 66b4f7f2..e3d42c73 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -64,6 +64,10 @@ def test_android(mocker: MockerFixture, params: dict[str, Any], func: str) -> No "user_music_dir": "/storage/emulated/0/Music", "user_desktop_dir": "/storage/emulated/0/Desktop", "user_projects_dir": "/storage/emulated/0/Projects", + "user_publicshare_dir": "/storage/emulated/0/Public", + "user_templates_dir": "/storage/emulated/0/Templates", + "user_fonts_dir": "/storage/emulated/0/fonts", + "user_preference_dir": f"/data/data/com.example/shared_prefs{suffix}", "user_bin_dir": "/data/data/com.example/files/bin", "site_bin_dir": "/data/data/com.example/files/bin", "user_applications_dir": f"/data/data/com.example/files{suffix}", diff --git a/tests/test_macos.py b/tests/test_macos.py index 3fd06b05..7519425c 100644 --- a/tests/test_macos.py +++ b/tests/test_macos.py @@ -84,6 +84,10 @@ def test_macos(mocker: MockerFixture, params: dict[str, Any], func: str) -> None "user_music_dir": f"{home}/Music", "user_desktop_dir": f"{home}/Desktop", "user_projects_dir": f"{home}/Projects", + "user_publicshare_dir": f"{home}/Public", + "user_templates_dir": f"{home}/Templates", + "user_fonts_dir": f"{home}/Library/Fonts", + "user_preference_dir": f"{home}/Library/Preferences{suffix}", "user_bin_dir": f"{home}/.local/bin", "site_bin_dir": "/usr/local/bin", "user_applications_dir": f"{home}/Applications", @@ -274,6 +278,10 @@ def test_macos_xdg_empty_falls_back( "user_music_dir": f"{home}/Music", "user_desktop_dir": f"{home}/Desktop", "user_projects_dir": f"{home}/Projects", + "user_publicshare_dir": f"{home}/Public", + "user_templates_dir": f"{home}/Templates", + "user_fonts_dir": f"{home}/Library/Fonts", + "user_preference_dir": f"{home}/Library/Preferences", "user_bin_dir": f"{home}/.local/bin", "site_bin_dir": "/usr/local/bin", "user_applications_dir": f"{home}/Applications", diff --git a/tests/test_unix.py b/tests/test_unix.py index a85e9079..c94db299 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -34,6 +34,8 @@ def _reload_after_test() -> typing.Iterator[None]: "user_music_dir", "user_desktop_dir", "user_projects_dir", + "user_publicshare_dir", + "user_templates_dir", ], ) def test_user_media_dir(mocker: MockerFixture, prop: str) -> None: @@ -53,6 +55,8 @@ def test_user_media_dir(mocker: MockerFixture, prop: str) -> None: pytest.param("XDG_MUSIC_DIR", "user_music_dir", id="user_music_dir"), pytest.param("XDG_DESKTOP_DIR", "user_desktop_dir", id="user_desktop_dir"), pytest.param("XDG_PROJECTS_DIR", "user_projects_dir", id="user_projects_dir"), + pytest.param("XDG_PUBLICSHARE_DIR", "user_publicshare_dir", id="user_publicshare_dir"), + pytest.param("XDG_TEMPLATES_DIR", "user_templates_dir", id="user_templates_dir"), ], ) def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str) -> None: @@ -76,6 +80,8 @@ def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str) pytest.param("XDG_MUSIC_DIR", "user_music_dir", "/home/example/Music", id="user_music_dir"), pytest.param("XDG_DESKTOP_DIR", "user_desktop_dir", "/home/example/Desktop", id="user_desktop_dir"), pytest.param("XDG_PROJECTS_DIR", "user_projects_dir", "/home/example/Projects", id="user_projects_dir"), + pytest.param("XDG_PUBLICSHARE_DIR", "user_publicshare_dir", "/home/example/Public", id="user_publicshare_dir"), + pytest.param("XDG_TEMPLATES_DIR", "user_templates_dir", "/home/example/Templates", id="user_templates_dir"), ], ) def test_user_media_dir_default(mocker: MockerFixture, env_var: str, prop: str, default_abs_path: str) -> None: @@ -94,6 +100,21 @@ def test_user_media_dir_default(mocker: MockerFixture, env_var: str, prop: str, assert getattr(Unix(), prop) == default_abs_path +def test_user_fonts_dir_default(mocker: MockerFixture) -> None: + mocker.patch.dict(os.environ, {"XDG_DATA_HOME": "", "HOME": "/home/example", "USERPROFILE": "/home/example"}) + assert Unix().user_fonts_dir == "/home/example/.local/share/fonts" + + +def test_user_fonts_dir_xdg_data_home(mocker: MockerFixture) -> None: + mocker.patch.dict(os.environ, {"XDG_DATA_HOME": "/custom/data"}) + assert Unix().user_fonts_dir == "/custom/data/fonts" + + +def test_user_preference_dir_is_config_dir() -> None: + dirs = Unix(appname="MyApp", version="1.0") + assert dirs.user_preference_dir == dirs.user_config_dir + + class XDGVariable(typing.NamedTuple): name: str default_value: str diff --git a/tests/test_windows.py b/tests/test_windows.py index 88767a3f..c7f99f31 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -5,6 +5,7 @@ import os import pathlib import sys +from pathlib import Path from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock @@ -92,6 +93,14 @@ def test_windows(params: dict[str, Any], func: str) -> None: "user_music_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_MYMUSIC"]), "user_desktop_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_DESKTOPDIRECTORY"]), "user_projects_dir": os.path.normpath(pathlib.Path("~/Projects").expanduser()), + "user_publicshare_dir": os.path.normpath( + os.environ.get("PUBLIC", str(Path("~").expanduser().parent / "Public")) + ), + "user_templates_dir": os.path.normpath( + str(Path(_WIN_FOLDERS["CSIDL_APPDATA"]) / "Microsoft" / "Windows" / "Templates") + ), + "user_fonts_dir": os.path.normpath(str(Path(_LOCAL) / "Microsoft" / "Windows" / "Fonts")), + "user_preference_dir": local, "user_bin_dir": os.path.join(_LOCAL, "Programs"), # noqa: PTH118 "site_bin_dir": os.path.join(_COMMON, "bin"), # noqa: PTH118 "user_applications_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_PROGRAMS"]),