Repository: useblocks/sphinx-needs
Pull request #1239 (2024-08-26 09:28:48)

🔧 Improve `NeedsConfigException` messages (hint how to fix)
It took me an emberassingly long time to figure out what was going on, since I had forgotten that I changed the config. I was searching my .rst files and for some hidden caches ...


Patch:
diff --git a/sphinx_needs/data.py b/sphinx_needs/data.py
index d2c64432..05bc0bd4 100644
--- a/sphinx_needs/data.py
+++ b/sphinx_needs/data.py
@@ -277,7 +277,7 @@ NeedsCoreFields: Final[Mapping[str, CoreFieldParameters]] = {
     },
     "section_name": {
         "description": "Simply the first section.",
-        "schema": {"type": ["string", "null"], "default": ""},
+        "schema": {"type": "string", "default": ""},
     },
     "signature": {
         "description": "Derived from a docutils desc_name node.",
diff --git a/sphinx_needs/needs.py b/sphinx_needs/needs.py
index b8501902..a88142bd 100644
--- a/sphinx_needs/needs.py
+++ b/sphinx_needs/needs.py
@@ -599,12 +599,12 @@ def check_configuration(_app: Sphinx, config: Config) -> None:
         if internal in extra_options:
             raise NeedsConfigException(
                 f'Extra option "{internal}" already used internally. '
-                " Please use another name."
+                " Please use another name in your config (needs_extra_options)."
             )
         if internal in link_types:
             raise NeedsConfigException(
                 f'Link type name "{internal}" already used internally. '
-                " Please use another name."
+                " Please use another name in your config (needs_extra_links)."
             )
 
     # Check if option and link are using the same name


Test patch:
diff --git a/tests/__snapshots__/test_basic_doc.ambr b/tests/__snapshots__/test_basic_doc.ambr
index 88e3fb6e..ed1d8091 100644
--- a/tests/__snapshots__/test_basic_doc.ambr
+++ b/tests/__snapshots__/test_basic_doc.ambr
@@ -446,10 +446,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/__snapshots__/test_export_id.ambr b/tests/__snapshots__/test_export_id.ambr
index d1940e46..4ad4c7d7 100644
--- a/tests/__snapshots__/test_export_id.ambr
+++ b/tests/__snapshots__/test_export_id.ambr
@@ -1042,10 +1042,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/__snapshots__/test_external.ambr b/tests/__snapshots__/test_external.ambr
index db8ed5ea..621f255b 100644
--- a/tests/__snapshots__/test_external.ambr
+++ b/tests/__snapshots__/test_external.ambr
@@ -346,10 +346,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
@@ -1123,10 +1120,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/__snapshots__/test_import.ambr b/tests/__snapshots__/test_import.ambr
index 4b55c941..32006133 100644
--- a/tests/__snapshots__/test_import.ambr
+++ b/tests/__snapshots__/test_import.ambr
@@ -4845,10 +4845,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/__snapshots__/test_need_constraints.ambr b/tests/__snapshots__/test_need_constraints.ambr
index a0524690..0d7cbd30 100644
--- a/tests/__snapshots__/test_need_constraints.ambr
+++ b/tests/__snapshots__/test_need_constraints.ambr
@@ -882,10 +882,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/__snapshots__/test_needextend.ambr b/tests/__snapshots__/test_needextend.ambr
index d17d5c61..ff62ef5f 100644
--- a/tests/__snapshots__/test_needextend.ambr
+++ b/tests/__snapshots__/test_needextend.ambr
@@ -650,10 +650,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
@@ -1507,10 +1504,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/__snapshots__/test_needs_builder.ambr b/tests/__snapshots__/test_needs_builder.ambr
index 52f9a73f..a474441b 100644
--- a/tests/__snapshots__/test_needs_builder.ambr
+++ b/tests/__snapshots__/test_needs_builder.ambr
@@ -514,10 +514,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
@@ -1174,10 +1171,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
@@ -1979,10 +1973,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/__snapshots__/test_service_github.ambr b/tests/__snapshots__/test_service_github.ambr
index 7e4b113b..e169c7ff 100644
--- a/tests/__snapshots__/test_service_github.ambr
+++ b/tests/__snapshots__/test_service_github.ambr
@@ -628,10 +628,7 @@
               'default': '',
               'description': 'Simply the first section.',
               'field_type': 'core',
-              'type': list([
-                'string',
-                'null',
-              ]),
+              'type': 'string',
             }),
             'sections': dict({
               'default': list([
diff --git a/tests/test_data.py b/tests/test_data.py
index e7f38c0d..9cc6f62b 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -1,5 +1,6 @@
 import ast
 import inspect
+from typing import ForwardRef
 
 from sphinx_needs.data import NeedsCoreFields, NeedsInfoType
 
@@ -10,8 +11,42 @@ def test_consistent():
     but I'm not sure this is possible (to encode both the static and dynamic data required).
     So at least here, we check that they are consistent with each other.
     """
+    # check fields are consistent
     assert set(NeedsCoreFields).issubset(set(NeedsInfoType.__annotations__))
 
+    # check field types are consistent with schema
+    for field, data in NeedsCoreFields.items():
+        if not (schema := data.get("schema")):
+            continue
+        type_ = NeedsInfoType.__annotations__[field]
+        assert isinstance(type_, ForwardRef)
+        type_str = type_.__forward_arg__
+        if type_str.startswith("Required"):
+            type_str = type_str[9:-1]
+        if type_str == "str" or type_str == "str | Text":
+            assert schema["type"] == "string", field
+        elif type_str == "int":
+            assert schema["type"] == "integer", field
+        elif type_str == "bool":
+            assert schema["type"] == "boolean", field
+        elif type_str in ("str | None", "None | str"):
+            assert schema["type"] == ["string", "null"], field
+        elif type_str in ("int | None", "None | int"):
+            assert schema["type"] == ["integer", "null"], field
+        elif type_str in ("bool | None", "None | bool"):
+            assert schema["type"] == ["boolean", "null"], field
+        elif type_str == "list[str]":
+            assert schema["type"] == "array", field
+            assert schema["items"]["type"] == "string", field
+        elif type_str == "dict[str, str]":
+            assert schema["type"] == "object", field
+            assert schema["additionalProperties"]["type"] == "string", field
+        elif type_str.startswith("dict["):
+            assert schema["type"] == "object", field
+        else:
+            raise ValueError(f"Unknown type: {type_str!r} for {field!r}")
+
+    # check descriptions are consistent
     source = inspect.getsource(NeedsInfoType)
     klass = ast.parse(source).body[0]
     descriptions = {}