Repository: astropy/astropy

Issue:
Wheels should not get build if unrelated labels are changed
### Description

As noticed in https://github.com/astropy/astropy/pull/17105#issuecomment-2387078145, when one changes any label (e.g., to make the changelog checker happy or get it to run again), all the wheels get rebuild too. This seems a waste of CI and of time, so ideally we fix it. Not a very high priority, obviously, but as all the other CI doesn't get triggered, hopefully it is not too much work. Note that the "Extra CI" runs do not suffer from this, so maybe the difference in setup with those can give a clue how to fix this.

### Expected behavior

_No response_

### How to Reproduce

1. Get package from '...'
2. Then run '...'
3. An error occurs.

```python
# Put your Python code snippet here.
```


### Versions

```python
import astropy
try:
    astropy.system_info()
except AttributeError:
    import platform; print(platform.platform())
    import sys; print("Python", sys.version)
    import astropy; print("astropy", astropy.__version__)
    import numpy; print("Numpy", numpy.__version__)
    import erfa; print("pyerfa", erfa.__version__)
    try:
        import scipy
        print("Scipy", scipy.__version__)
    except ImportError:
        print("Scipy not installed")
    try:
        import matplotlib
        print("Matplotlib", matplotlib.__version__)
    except ImportError:
        print("Matplotlib not installed")
```
```
# Paste the result here
```


Discussion:
github-actions[bot]: Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  - [ ] Do the proposed changes actually accomplish desired goals?
  - [ ] Do the proposed changes follow the [Astropy coding guidelines](https://docs.astropy.org/en/latest/development/codeguide.html)?
  - [ ] Are tests added/updated as required? If so, do they follow the [Astropy testing guidelines](https://docs.astropy.org/en/latest/development/testguide.html)?
  - [ ] Are docs added/updated as required? If so, do they follow the [Astropy documentation guidelines](https://docs.astropy.org/en/latest/development/docguide.html)?
  - [ ] Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for [rebase](https://docs.astropy.org/en/latest/development/development_details.html#rebase-if-necessary) and [squash](https://docs.astropy.org/en/latest/development/development_details.html#squash-if-necessary).
  - [ ] Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the [bot](https://docs.astropy.org/en/latest/development/development_details.html#pre-commit).
  - [ ] Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  - [ ] Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  - [ ] At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) *before* merge.

Patch:
diff --git a/.github/workflows/ci_benchmark.yml b/.github/workflows/ci_benchmark.yml
index 83ddb69775..338845da67 100644
--- a/.github/workflows/ci_benchmark.yml
+++ b/.github/workflows/ci_benchmark.yml
@@ -15,7 +15,7 @@ permissions:
 
 jobs:
   benchmark:
-    if: github.repository == 'astropy/astropy' && contains(github.event.pull_request.labels.*.name, 'benchmark')
+    if: (github.repository == 'astropy/astropy' && ((github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'benchmark')) || (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'benchmark')))
     name: "Compare asv with astropy main"
     runs-on: ubuntu-latest
     env:
diff --git a/.github/workflows/ci_cron_daily.yml b/.github/workflows/ci_cron_daily.yml
index 6021e0943a..612a507144 100644
--- a/.github/workflows/ci_cron_daily.yml
+++ b/.github/workflows/ci_cron_daily.yml
@@ -35,7 +35,7 @@ permissions:
 jobs:
   tests:
     runs-on: ${{ matrix.os }}
-    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Extra CI')))
+    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'Extra CI')) || (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'Extra CI')))
     strategy:
       fail-fast: false
       matrix:
diff --git a/.github/workflows/ci_cron_weekly.yml b/.github/workflows/ci_cron_weekly.yml
index 705e6e4e7e..8efc4aedd4 100644
--- a/.github/workflows/ci_cron_weekly.yml
+++ b/.github/workflows/ci_cron_weekly.yml
@@ -31,7 +31,7 @@ env:
 jobs:
   tests:
     runs-on: ${{ matrix.os }}
-    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Extra CI')))
+    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'Extra CI')) || (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'Extra CI')))
     env:
       ARCH_ON_CI: "normal"
     strategy:
@@ -108,7 +108,7 @@ jobs:
     runs-on: ubuntu-latest
     name: Python 3.12
     # keep condition in sync with test_arm64
-    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Extra CI')))
+    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'Extra CI')) || (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'Extra CI')))
     env:
       ARCH_ON_CI: ${{ matrix.arch }}
 
@@ -180,7 +180,9 @@ jobs:
     runs-on: linux-arm64
     name: Python 3.12 (arm64)
     # keep condition in sync with test_more_architectures
-    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Extra CI')))
+    if: (github.repository == 'astropy/astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'Extra CI')) || (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'Extra CI')))
+    env:
+      ARCH_ON_CI: "arm64"
 
     steps:
       - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938  # v4.2.0
@@ -191,6 +193,5 @@ jobs:
           python-version: '3.12'
       - name: Set up dependencies
         run: pip install tox
-
       - name: Run tests
         run: tox -e py312-test -- -n=2
diff --git a/.github/workflows/ci_workflows.yml b/.github/workflows/ci_workflows.yml
index 464051ce4b..e13a686805 100644
--- a/.github/workflows/ci_workflows.yml
+++ b/.github/workflows/ci_workflows.yml
@@ -110,3 +110,17 @@ jobs:
         - name: (Allowed Failure) Python 3.12 with remote data and dev version of key dependencies
           linux: py312-test-devdeps
           posargs: --remote-data=any --verbose
+
+  test_wheel_building:
+    needs: [initial_checks]
+    # This ensures that a couple of wheel targets work fine in pull requests and pushes
+    permissions:
+      contents: none
+    uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish.yml@924441154cf3053034c6513d5e06c69d262fb9a6  # v1.13.0
+    with:
+      upload_to_pypi: false
+      upload_to_anaconda: false
+      test_extras: test
+      test_command: pytest -p no:warnings --astropy-header -m "not hypothesis" -k "not test_data_out_of_range and not test_set_locale and not TestQuantityTyping" --pyargs astropy
+      targets: |
+        - cp311-manylinux_x86_64
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 431649d732..2828b7d1ca 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -21,28 +21,13 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
-
-  test_wheel_building:
-    # This ensures that a couple of targets work fine in pull requests and pushes
-    permissions:
-      contents: none
-    uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish.yml@924441154cf3053034c6513d5e06c69d262fb9a6  # v1.13.0
-    if: (github.event_name == 'push' || github.event_name == 'pull_request')
-    with:
-      upload_to_pypi: false
-      upload_to_anaconda: false
-      test_extras: test
-      test_command: pytest -p no:warnings --astropy-header -m "not hypothesis" -k "not test_data_out_of_range and not test_set_locale and not TestQuantityTyping" --pyargs astropy
-      targets: |
-        - cp311-manylinux_x86_64
-
   build_and_publish:
     # This does the actual wheel building and publishing as part of the cron job
     # or if triggered manually via the workflow dispatch, or for a tag.
     permissions:
       contents: none
     uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish.yml@924441154cf3053034c6513d5e06c69d262fb9a6  # v1.13.0
-    if: (github.repository == 'astropy/astropy' && ( startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build all wheels')))
+    if: (github.repository == 'astropy/astropy' && ( startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'Build all wheels')) || (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'Build all wheels')))
     with:
 
       # We upload to PyPI for all tags starting with v but not ones ending in .dev
diff --git a/astropy/config/configuration.py b/astropy/config/configuration.py
index 00f7681cfb..ecafcdef23 100644
--- a/astropy/config/configuration.py
+++ b/astropy/config/configuration.py
@@ -24,7 +24,7 @@ from astropy.extern.configobj import configobj, validate
 from astropy.utils import find_current_module, silence
 from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyWarning
 
-from .paths import get_config_dir
+from .paths import get_config_dir_path
 
 __all__ = (
     "InvalidConfigurationItemWarning",
@@ -611,7 +611,7 @@ def get_config(packageormod=None, reload=False, rootname=None):
                 cfgfn = Path(_override_config_file)
             else:
                 cfgfn = (
-                    Path(get_config_dir(rootname=rootname))
+                    get_config_dir_path(rootname=rootname)
                     .joinpath(pkgname)
                     .with_suffix(".cfg")
                 )
diff --git a/astropy/config/paths.py b/astropy/config/paths.py
index 3541bb3f69..88646962a3 100644
--- a/astropy/config/paths.py
+++ b/astropy/config/paths.py
@@ -9,10 +9,17 @@ import sys
 from functools import wraps
 from pathlib import Path
 
-__all__ = ["get_config_dir", "get_cache_dir", "set_temp_config", "set_temp_cache"]
+__all__ = [
+    "get_config_dir",
+    "get_config_dir_path",
+    "get_cache_dir",
+    "get_cache_dir_path",
+    "set_temp_config",
+    "set_temp_cache",
+]
 
 
-def get_config_dir(rootname: str = "astropy") -> str:
+def get_config_dir_path(rootname: str = "astropy") -> Path:
     """
     Determines the package configuration directory name and creates the
     directory if it doesn't exist.
@@ -31,7 +38,7 @@ def get_config_dir(rootname: str = "astropy") -> str:
 
     Returns
     -------
-    configdir : str
+    configdir : Path
         The absolute path to the configuration directory.
 
     """
@@ -43,7 +50,7 @@ def get_config_dir(rootname: str = "astropy") -> str:
         xch = set_temp_config._temp_path
         config_path = xch / rootname
         config_path.mkdir(exist_ok=True)
-        return str(config_path.resolve())
+        return config_path.resolve()
 
     # first look for XDG_CONFIG_HOME
     if (
@@ -52,14 +59,29 @@ def get_config_dir(rootname: str = "astropy") -> str:
         and not (xchpth := xch / rootname).is_symlink()
     ):
         if xchpth.exists():
-            return str(xchpth.resolve())
+            return xchpth.resolve()
         else:
             linkto = xchpth
 
-    return str(_find_or_create_root_dir("config", linkto, rootname))
+    return _find_or_create_root_dir("config", linkto, rootname)
 
 
-def get_cache_dir(rootname: str = "astropy") -> str:
+def get_config_dir(rootname: str = "astropy") -> str:
+    return str(get_config_dir_path(rootname))
+
+
+get_config_dir.__doc__ = (
+    get_config_dir_path.__doc__
+    + """
+    See Also
+    --------
+    get_config_dir_path : same as this function, except that the return value is a pathlib.Path
+
+    """
+)
+
+
+def get_cache_dir_path(rootname: str = "astropy") -> Path:
     """
     Determines the Astropy cache directory name and creates the directory if it
     doesn't exist.
@@ -78,7 +100,7 @@ def get_cache_dir(rootname: str = "astropy") -> str:
 
     Returns
     -------
-    cachedir : str
+    cachedir : Path
         The absolute path to the cache directory.
 
     """
@@ -91,7 +113,7 @@ def get_cache_dir(rootname: str = "astropy") -> str:
         cache_path = xch / rootname
         if not cache_path.is_file():
             cache_path.mkdir(exist_ok=True)
-        return str(cache_path.resolve())
+        return cache_path.resolve()
 
     # first look for XDG_CACHE_HOME
     if (
@@ -100,11 +122,26 @@ def get_cache_dir(rootname: str = "astropy") -> str:
         and not (xchpth := xch / rootname).is_symlink()
     ):
         if xchpth.exists():
-            return str(xchpth.resolve())
+            return xchpth.resolve()
         else:
             linkto = xchpth
 
-    return str(_find_or_create_root_dir("cache", linkto, rootname))
+    return _find_or_create_root_dir("cache", linkto, rootname)
+
+
+def get_cache_dir(rootname: str = "astropy") -> str:
+    return str(get_cache_dir_path(rootname))
+
+
+get_cache_dir.__doc__ = (
+    get_cache_dir_path.__doc__
+    + """
+    See Also
+    --------
+    get_cache_dir_path : same as this function, except that the return value is a pathlib.Path
+
+    """
+)
 
 
 class _SetTempPath:
diff --git a/astropy/logger.py b/astropy/logger.py
index 83c500c231..934ee8a76a 100644
--- a/astropy/logger.py
+++ b/astropy/logger.py
@@ -525,7 +525,7 @@ class AstropyLogger(Logger):
             try:
                 if log_file_path == "" or testing_mode:
                     log_file_path = (
-                        Path(_config.get_config_dir("astropy")) / "astropy.log"
+                        _config.get_config_dir_path("astropy") / "astropy.log"
                     )
                 else:
                     log_file_path = Path(log_file_path).expanduser()
diff --git a/astropy/utils/data.py b/astropy/utils/data.py
index 9311452428..2866851079 100644
--- a/astropy/utils/data.py
+++ b/astropy/utils/data.py
@@ -1883,17 +1883,15 @@ def _get_download_cache_loc(pkgname="astropy"):
         The path to the data cache directory.
     """
     try:
-        datadir = os.path.join(
-            astropy.config.paths.get_cache_dir(pkgname), "download", "url"
-        )
+        datadir = astropy.config.paths.get_cache_dir_path(pkgname) / "download" / "url"
 
-        if not os.path.exists(datadir):
+        if not datadir.exists():
             try:
-                os.makedirs(datadir)
+                datadir.mkdir(parents=True)
             except OSError:
-                if not os.path.exists(datadir):
+                if not datadir.exists():
                     raise
-        elif not os.path.isdir(datadir):
+        elif not datadir.is_dir():
             raise OSError(f"Data cache directory {datadir} is not a directory")
 
         return datadir
diff --git a/docs/changes/config/17118.feature.rst b/docs/changes/config/17118.feature.rst
new file mode 100644
index 0000000000..648f487444
--- /dev/null
+++ b/docs/changes/config/17118.feature.rst
@@ -0,0 +1,3 @@
+Added ``get_config_dir_path`` (and ``get_cache_dir_path``) which is equivalent
+to ``get_config_dir`` (respectively ``get_cache_dir``) except that it returns a
+``pathlib.Path`` object instead of ``str``.


Test patch:
diff --git a/astropy/config/tests/test_configs.py b/astropy/config/tests/test_configs.py
index ca3a4bbdf0..99997fb784 100644
--- a/astropy/config/tests/test_configs.py
+++ b/astropy/config/tests/test_configs.py
@@ -28,6 +28,10 @@ def teardown_module():
 def test_paths():
     assert "astropy" in paths.get_config_dir()
     assert "astropy" in paths.get_cache_dir()
+    assert str(paths.get_config_dir_path()) == paths.get_config_dir()
+    assert str(paths.get_cache_dir_path()) == paths.get_cache_dir()
+    assert paths.get_config_dir_path().is_absolute()
+    assert paths.get_cache_dir_path().is_absolute()
 
     assert "testpkg" in paths.get_config_dir(rootname="testpkg")
     assert "testpkg" in paths.get_cache_dir(rootname="testpkg")
@@ -47,6 +51,7 @@ def test_set_temp_config(tmp_path, monkeypatch):
     @paths.set_temp_config(temp_config_dir)
     def test_func():
         assert paths.get_config_dir(rootname="astropy") == str(temp_astropy_config)
+        assert paths.get_config_dir_path(rootname="astropy") == temp_astropy_config
 
         # Test temporary restoration of original default
         with paths.set_temp_config() as d:
@@ -57,6 +62,7 @@ def test_set_temp_config(tmp_path, monkeypatch):
     # Test context manager mode (with cleanup)
     with paths.set_temp_config(temp_config_dir, delete=True):
         assert paths.get_config_dir(rootname="astropy") == str(temp_astropy_config)
+        assert paths.get_config_dir_path(rootname="astropy") == temp_astropy_config
 
     assert not temp_config_dir.exists()
     # Check that we have returned to our old configuration.
@@ -414,10 +420,13 @@ def test_config_noastropy_fallback(monkeypatch):
     # also have to make sure the stored configuration objects are cleared
     monkeypatch.setattr(configuration, "_cfgobjs", {})
 
+    # make sure the config dir search fails
     with pytest.raises(OSError):
-        # make sure the config dir search fails
         paths.get_config_dir(rootname="astropy")
 
+    with pytest.raises(OSError):
+        paths.get_config_dir_path(rootname="astropy")
+
     # now run the basic tests, and make sure the warning about no astropy
     # is present
     test_configitem()
diff --git a/astropy/utils/tests/test_data.py b/astropy/utils/tests/test_data.py
index 9a4cca5236..833c0ab1bd 100644
--- a/astropy/utils/tests/test_data.py
+++ b/astropy/utils/tests/test_data.py
@@ -1065,10 +1065,13 @@ def test_data_noastropy_fallback(monkeypatch):
 
     monkeypatch.setattr(paths, "_find_or_create_root_dir", osraiser)
 
+    # make sure the config dir search fails
     with pytest.raises(OSError):
-        # make sure the config dir search fails
         paths.get_cache_dir(rootname="astropy")
 
+    with pytest.raises(OSError):
+        paths.get_cache_dir_path(rootname="astropy")
+
     with pytest.warns(CacheMissingWarning) as warning_lines:
         fnout = download_file(TESTURL, cache=True)
     n_warns = len(warning_lines)