Repository: gerlero/foamlib

Issue:
Setting a key "n" fails
Strangely the SubDict code doesn't like keyword "n" in particular as an edge case. Here's a minimal example:
```
def test(case: Path):
    decomposeParDict = FoamFile(case / "system/decomposeParDict")
    decomposeParDict["coeffs"]["n"] = [2, 2, 2]
```

When I run it against an existing `system/decomposeParDict`, I get an exception in `__setitem__`:
```
ValueError: Invalid keyword: n
```

The same code works correctly if I use `m` as the key. It fails regardless whether the "coeffs/n" entry is already in the file or not.

For the record, my `system/decomposeParDict` file looked like this:
```
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      decomposeParDict;
}

numberOfSubdomains 12;

method          hierarchical;

coeffs
{
    n           (3 2 2);
}
```

Hints from issue comments:
@gerlero: @kgoba Thanks for reporting. This is likely foamlib interpreting `"n"` as a shorthand for `"no"`, and thus converting it to `False` (even though it shouldn't and `False` is not a valid keyword). I will look into it.@gerlero: foamlib `0.8.9` is now out and should fix this issue. Thanks again for the report.

Solution patch:
diff --git a/foamlib/_files/_files.py b/foamlib/_files/_files.py
index 334ce94..c493202 100644
--- a/foamlib/_files/_files.py
+++ b/foamlib/_files/_files.py
@@ -261,7 +261,7 @@ class FoamFile(
         elif not isinstance(keywords, tuple):
             keywords = (keywords,)
 
-        if keywords and not isinstance(normalize(keywords[-1]), str):
+        if keywords and not isinstance(normalize(keywords[-1], kind=Kind.KEYWORD), str):
             msg = f"Invalid keyword: {keywords[-1]}"
             raise ValueError(msg)
 
diff --git a/foamlib/_files/_serialization.py b/foamlib/_files/_serialization.py
index 97ecb9d..09133de 100644
--- a/foamlib/_files/_serialization.py
+++ b/foamlib/_files/_serialization.py
@@ -31,6 +31,7 @@ class Kind(Enum):
     BINARY_FIELD = auto()
     SCALAR_BINARY_FIELD = auto()
     DIMENSIONS = auto()
+    KEYWORD = auto()
 
 
 @overload
@@ -91,7 +92,10 @@ def normalize(data: EntryLike, *, kind: Kind = Kind.DEFAULT) -> Entry:
         return [normalize(d, kind=Kind.SINGLE_ENTRY) for d in data]
 
     if isinstance(data, str):
-        return parse_data(data)
+        parsed_data = parse_data(data)
+        if kind == Kind.KEYWORD and isinstance(parsed_data, bool):
+            return data
+        return parsed_data
 
     if isinstance(
         data,


Test patch:
diff --git a/tests/test_files/test_files.py b/tests/test_files/test_files.py
index 78c63f9..1f8e405 100644
--- a/tests/test_files/test_files.py
+++ b/tests/test_files/test_files.py
@@ -86,6 +86,11 @@ def test_write_read(tmp_path: Path) -> None:
         value=[0, 0, -9.81],
     )
 
+    sd["n"] = 1
+    sd["y"] = 2
+    assert sd["n"] == 1
+    assert sd["y"] == 2
+
     with d:
         lst = d["subdict", "list"]
         assert isinstance(lst, list)