Repository: scikit-build/scikit-build-core (python)

Issue:
pyproject_metadata not found
I got this:
```
      from pyproject_metadata import StandardMetadata
  ModuleNotFoundError: No module named 'pyproject_metadata'
```
I can see in your `pyproject.toml` has this as an optional dependency?

I don't know why this happens, but is there a reason why it isn't a required dependency?

I am trying to put *everything* into the `pyproject.toml` :)

I did:
```
python -m pip install scikit-build-core
```
and tried it, but fails with the above. It of course continues if `pyproject-metadata` also gets installed :)


Pull request:
fix: always require pathspec
This adds `pathspec` as a required dependency. It makes the native backend easier to use, and is already there for hatchling, so it's really only an extra dependency for custom plugins and setuptools, so I think it's a valid tradeoff now that pyproject-metadata is vendored. 

Closes #280.

- **chore: remove a few unused pyproject-metadata's**
- **fix: always require pathspec**


Patch:
diff --git a/README.md b/README.md
index 969c81c..8c0b8a1 100644
--- a/README.md
+++ b/README.md
@@ -112,9 +112,7 @@ version = "0.0.1"
 ```
 
 You can (and should) specify the rest of the entries in `project`, but these are
-the minimum to get started. You can also `scikit-build-core[pyproject]` to
-pre-load some dependencies if you want; in some cases this might be marginally
-faster.
+the minimum to get started.
 
 An example `CMakeLists.txt`:
 
diff --git a/docs/examples/downstream/nanobind_example/CMakeLists.txt b/docs/examples/downstream/nanobind_example/CMakeLists.txt
index 2b8f6a3..ba3ee22 100644
--- a/docs/examples/downstream/nanobind_example/CMakeLists.txt
+++ b/docs/examples/downstream/nanobind_example/CMakeLists.txt
@@ -19,7 +19,7 @@ if(NOT SKBUILD)
   in your environment once and use the following command that avoids
   a costly creation of a new virtual environment at every compilation:
   =====================================================================
-   $ pip install nanobind scikit-build-core[pyproject]
+   $ pip install nanobind scikit-build-core
    $ pip install --no-build-isolation -ve .
   =====================================================================
   You may optionally add -Ceditable.rebuild=true to auto-rebuild when
diff --git a/noxfile.py b/noxfile.py
index c411677..aec21c7 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -134,7 +134,7 @@ def docs(session: nox.Session) -> None:
 
     serve = args.builder == "html" and session.interactive
     extra_installs = ["sphinx-autobuild"] if serve else []
-    session.install("-e.[docs,pyproject]", *extra_installs)
+    session.install("-e.[docs]", *extra_installs)
 
     session.chdir("docs")
 
@@ -229,7 +229,7 @@ def downstream(session: nox.Session) -> None:
     proj_dir = tmp_dir / "_".join(args.project.split("/"))
 
     session.install("build", "hatch-vcs", "hatchling")
-    session.install(".[pyproject]", "--no-build-isolation")
+    session.install(".", "--no-build-isolation")
 
     if proj_dir.is_dir():
         session.chdir(proj_dir)
diff --git a/pyproject.toml b/pyproject.toml
index 04a55bc..60fd719 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,6 +37,7 @@ dependencies = [
     "importlib-metadata >=4.13; python_version<'3.8'",
     "importlib-resources >=1.3; python_version<'3.9'",
     "packaging >=21.3",
+    "pathspec >=0.10.1",
     "tomli >=1.2.2; python_version<'3.11'",
     "typing-extensions >=3.10.0; python_version<'3.9'",
 ]
@@ -46,16 +47,12 @@ dependencies = [
 
 [project.optional-dependencies]
 pyproject = [
-    "pathspec >=0.10.1",
-    "pyproject-metadata >=0.6.1",
 ]
 test = [
     "build >=0.8",
     "cattrs >=22.2.0",
-    "pathspec >=0.10.1",
     "pip >=22",
     "pybind11 >=2.11",
-    "pyproject-metadata >=0.5",
     "pytest >=7.0",  # 7.2+ recommended for better tracebacks with ExceptionGroup
     "pytest-subprocess >=1.5",
     'setuptools >=43; python_version<"3.9"',
@@ -96,8 +93,6 @@ docs = [
     "furo",
     "hatchling",
     "myst-parser >=0.13",
-    "pathspec >=0.10.1",
-    "pyproject-metadata >=0.5",
     "setuptools",
     "sphinx >=7.0",
     "sphinx-autodoc-typehints",
diff --git a/src/scikit_build_core/build/__init__.py b/src/scikit_build_core/build/__init__.py
index 8da7f0c..ba477aa 100644
--- a/src/scikit_build_core/build/__init__.py
+++ b/src/scikit_build_core/build/__init__.py
@@ -109,7 +109,6 @@ def get_requires_for_build_sdist(
     )
 
     return [
-        "pathspec",
         *cmake_requires,
         *requires.dynamic_metadata(),
     ]
@@ -128,7 +127,6 @@ def get_requires_for_build_wheel(
     )
 
     return [
-        "pathspec",
         *cmake_requires,
         *requires.dynamic_metadata(),
     ]
@@ -147,7 +145,6 @@ def get_requires_for_build_editable(
     )
 
     return [
-        "pathspec",
         *cmake_requires,
         *requires.dynamic_metadata(),
     ]
diff --git a/src/scikit_build_core/settings/sources.py b/src/scikit_build_core/settings/sources.py
index 13eb3d5..ffc147b 100644
--- a/src/scikit_build_core/settings/sources.py
+++ b/src/scikit_build_core/settings/sources.py
@@ -314,12 +314,15 @@ class ConfSource:
     nested dicts. "verify" is a boolean that determines whether unrecognized
     options should be checked for. Only set this to false if this might be sharing
     config options at the same level.
+
+    While most mechanisms (pip, uv, build) only support text, gpep517 allows an
+    arbitrary json input, so this currently also handles bools.
     """
 
     def __init__(
         self,
         *prefixes: str,
-        settings: Mapping[str, str | list[str]],
+        settings: Mapping[str, str | list[str] | bool],
         verify: bool = True,
     ) -> None:
         self.prefixes = prefixes
@@ -339,7 +342,9 @@ class ConfSource:
 
         return name in self.settings
 
-    def get_item(self, *fields: str, is_dict: bool) -> str | list[str] | dict[str, str]:
+    def get_item(
+        self, *fields: str, is_dict: bool
+    ) -> str | list[str] | dict[str, str] | bool:
         names = self._get_name(*fields)
         name = ".".join(names)
         if is_dict:
@@ -360,7 +365,7 @@ class ConfSource:
 
     @classmethod
     def convert(
-        cls, item: str | list[str] | dict[str, str], target: type[Any]
+        cls, item: str | list[str] | dict[str, str] | bool, target: type[Any]
     ) -> object:
         target, _ = _process_annotated(target)
         raw_target = _get_target_raw_type(target)
@@ -370,20 +375,20 @@ class ConfSource:
         if raw_target is list:
             if isinstance(item, list):
                 return [cls.convert(i, _get_inner_type(target)) for i in item]
-            if isinstance(item, dict):
+            if isinstance(item, (dict, bool)):
                 msg = f"Expected {target}, got {type(item).__name__}"
                 raise TypeError(msg)
             return [
                 cls.convert(i.strip(), _get_inner_type(target)) for i in item.split(";")
             ]
         if raw_target is dict:
-            assert not isinstance(item, (str, list))
+            assert not isinstance(item, (str, list, bool))
             return {k: cls.convert(v, _get_inner_type(target)) for k, v in item.items()}
         if isinstance(item, (list, dict)):
             msg = f"Expected {target}, got {type(item).__name__}"
             raise TypeError(msg)
         if raw_target is bool:
-            return _process_bool(item)
+            return item if isinstance(item, bool) else _process_bool(item)
         if raw_target is Union and str in get_args(target):
             return item
         if raw_target is Literal:


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6f47e36..a4b73ce 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -44,7 +44,7 @@ def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path:
             "wheel",
             "--wheel-dir",
             str(wheelhouse),
-            f"{BASE}[pyproject]",
+            f"{BASE}",
         ],
         check=True,
     )
@@ -208,10 +208,10 @@ def package_simple_pyproject_ext(
 ) -> PackageInfo:
     package = PackageInfo(
         "simple_pyproject_ext",
-        "6c5bb5f1b6c44f3aab37fc8f768004f644c24001ad0304759a69b754ca6ad46d",
-        "ba13e4195ae6333459412bc802ba8c02c231cf1c5ee143f6a3392786d80a49b4",
-        "f1a6050eae910de7cd86830ba97af5e74ff953f9acc2adab47aa9cdf2a3eca91",
-        "b861db81fd1de6dee4a0d6574af27af54424fbccce12b018644d1db136f218ac",
+        "5544d96810ce60ac40baf28cf8caf2e1e7e1fa7439b283d3fb52cdc1f87f12ac",
+        "aaa15c185dc3fbc301dc2fca937cc935442c38e55bc400fbefd424bd6ce92adb",
+        "ee3a564a37c42df3abdcee3862175baceeb6f6eff0b29931681b424ec5d96067",
+        "4c1d402621e7f00fce4ce5afdb73a9ba4cc25cd4bb57619113432841f779dd68",
     )
     process_package(package, tmp_path, monkeypatch)
     return package
diff --git a/tests/packages/sdist_config/pyproject.toml b/tests/packages/sdist_config/pyproject.toml
index 6e3037a..d2c52e1 100644
--- a/tests/packages/sdist_config/pyproject.toml
+++ b/tests/packages/sdist_config/pyproject.toml
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["scikit-build-core[pyproject]"]
+requires = ["scikit-build-core"]
 build-backend = "scikit_build_core.build"
 
 [project]
diff --git a/tests/packages/simple_purelib_package/pyproject.toml b/tests/packages/simple_purelib_package/pyproject.toml
index a023663..c4e6646 100644
--- a/tests/packages/simple_purelib_package/pyproject.toml
+++ b/tests/packages/simple_purelib_package/pyproject.toml
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["scikit-build-core[pyproject]"]
+requires = ["scikit-build-core"]
 build-backend = "scikit_build_core.build"
 
 [project]
diff --git a/tests/packages/simple_pyproject_ext/pyproject.toml b/tests/packages/simple_pyproject_ext/pyproject.toml
index 01748c9..810a287 100644
--- a/tests/packages/simple_pyproject_ext/pyproject.toml
+++ b/tests/packages/simple_pyproject_ext/pyproject.toml
@@ -1,6 +1,6 @@
 [build-system]
 requires = [
-    "scikit_build_core[pyproject]",
+    "scikit_build_core",
     "pybind11",
 ]
 build-backend = "scikit_build_core.build"
diff --git a/tests/packages/simple_pyproject_source_dir/pyproject.toml b/tests/packages/simple_pyproject_source_dir/pyproject.toml
index 6ddac46..c4426a3 100644
--- a/tests/packages/simple_pyproject_source_dir/pyproject.toml
+++ b/tests/packages/simple_pyproject_source_dir/pyproject.toml
@@ -1,6 +1,6 @@
 [build-system]
 requires = [
-    "scikit_build_core[pyproject]",
+    "scikit_build_core",
     "pybind11",
 ]
 build-backend = "scikit_build_core.build"
diff --git a/tests/test_custom_modules.py b/tests/test_custom_modules.py
index 13c70db..d5e02b4 100644
--- a/tests/test_custom_modules.py
+++ b/tests/test_custom_modules.py
@@ -10,7 +10,7 @@ PROJECT_DIR = DIR / "packages" / "custom_cmake"
 
 
 def test_ep(isolated):
-    isolated.install("hatchling", "scikit-build-core[pyproject]")
+    isolated.install("hatchling", "scikit-build-core")
     isolated.install(PROJECT_DIR / "extern", isolated=False)
     isolated.install(PROJECT_DIR, "-v", isolated=False)
 
diff --git a/tests/test_editable.py b/tests/test_editable.py
index 6e71894..41ce2c8 100644
--- a/tests/test_editable.py
+++ b/tests/test_editable.py
@@ -28,7 +28,7 @@ def test_navigate_editable(isolated, isolate, package):
     isolate_args = ["--no-build-isolation"] if not isolate else []
     isolated.install("pip>=23")
     if not isolate:
-        isolated.install("scikit-build-core[pyproject]")
+        isolated.install("scikit-build-core")
 
     if package:
         init_py = Path("python/shared_pkg/data/__init__.py")
@@ -73,7 +73,7 @@ def test_cython_pxd(monkeypatch, tmp_path, editable, editable_mode, isolated):
     tmp_path1.mkdir()
     process_package(package1, tmp_path1, monkeypatch)
 
-    isolated.install("pip>23", "cython", "scikit-build-core[pyproject]")
+    isolated.install("pip>23", "cython", "scikit-build-core")
 
     isolated.install(
         "-v",
diff --git a/tests/test_get_requires.py b/tests/test_get_requires.py
index 5c916a5..cd431ed 100644
--- a/tests/test_get_requires.py
+++ b/tests/test_get_requires.py
@@ -116,11 +116,11 @@ def test_get_requires_for_build_sdist(fp):
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
     )
-    assert set(get_requires_for_build_sdist({})) == {"pathspec"}
+    assert set(get_requires_for_build_sdist({})) == set()
 
 
 def test_get_requires_for_build_sdist_cmake(fp):
-    expected = {"pathspec", "cmake>=3.15", *ninja}
+    expected = {"cmake>=3.15", *ninja}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
@@ -129,7 +129,7 @@ def test_get_requires_for_build_sdist_cmake(fp):
 
 
 def test_get_requires_for_build_wheel(fp):
-    expected = {"pathspec", "cmake>=3.15", *ninja}
+    expected = {"cmake>=3.15", *ninja}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
@@ -138,16 +138,15 @@ def test_get_requires_for_build_wheel(fp):
 
 
 def test_get_requires_for_build_wheel_pure(fp):
-    expected = {"pathspec"}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
     )
-    assert set(get_requires_for_build_wheel({"wheel.cmake": "False"})) == expected
+    assert set(get_requires_for_build_wheel({"wheel.cmake": "False"})) == set()
 
 
 def test_get_requires_for_build_editable(fp):
-    expected = {"pathspec", "cmake>=3.15", *ninja}
+    expected = {"cmake>=3.15", *ninja}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
@@ -156,9 +155,8 @@ def test_get_requires_for_build_editable(fp):
 
 
 def test_get_requires_for_build_editable_pure(fp):
-    expected = {"pathspec"}
     fp.register(
         [Path("cmake/path"), "-E", "capabilities"],
         stdout='{"version":{"string":"3.14.0"}}',
     )
-    assert set(get_requires_for_build_editable({"wheel.cmake": "False"})) == expected
+    assert set(get_requires_for_build_editable({"wheel.cmake": "False"})) == set()
diff --git a/tests/test_pyproject_pep660.py b/tests/test_pyproject_pep660.py
index c4ed403..390214a 100644
--- a/tests/test_pyproject_pep660.py
+++ b/tests/test_pyproject_pep660.py
@@ -59,7 +59,7 @@ def test_pep660_pip_isolated(isolated, isolate, editable_mode: str):
     isolate_args = ["--no-build-isolation"] if not isolate else []
     isolated.install("pip>=23")
     if not isolate:
-        isolated.install("scikit-build-core[pyproject]")
+        isolated.install("scikit-build-core")
 
     build_dir = "" if editable_mode == "inplace" else "build/{wheel_tag}"
 
diff --git a/tests/test_settings.py b/tests/test_settings.py
index d04887c..bee7bfe 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -345,6 +345,16 @@ def test_env_var_bools(monkeypatch, truthy, falsey):
     assert not settings.true
 
 
+def test_conf_settings_bools():
+    sources = SourceChain(
+        ConfSource(settings={"false": True, "true": False}),
+    )
+    settings = sources.convert_target(SettingBools)
+
+    assert settings.false
+    assert not settings.true
+
+
 @dataclasses.dataclass
 class SettingLists:
     list0: List[str] = dataclasses.field(default_factory=list)