Repository: yukinarit/pyserde
Pull request #549 (2024-06-12 13:42:11)

Raise error when flatten can not be used
Only dataclass and optional dataclass are supported at the moment


Patch:
diff --git a/Makefile b/Makefile
index 9b9e9d2..399a3b6 100644
--- a/Makefile
+++ b/Makefile
@@ -37,7 +37,6 @@ docs:
 	mkdir -p docs out/api out/guide/en
 	$(POETRY) run pdoc -e serde=https://github.com/yukinarit/pyserde/tree/main/serde/ serde -o out/api
 	mdbook build -d ../../out/guide/en ./docs/en
-	mdbook build -d ../../out/guide/ja ./docs/ja
 
 open-docs:
 	$(POETRY) run pdoc -e serde=https://github.com/yukinarit/pyserde/tree/main/serde serde
diff --git a/README.md b/README.md
index 409b11c..f07d7aa 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
     <a href = "https://piptrends.com/package/pyserde" alt = "pyserde Downloads Last Month"><img alt="pyserde Downloads Last Month by pip Trends" src="https://assets.piptrends.com/get-last-month-downloads-badge/pyserde.svg"></a>
 </p>
 <p align="center">
-  <a href="https://yukinarit.github.io/pyserde/guide/en">Guide🇬🇧</a> | <a href="https://yukinarit.github.io/pyserde/guide/ja">ガイド🇯🇵</a> | <a href="https://yukinarit.github.io/pyserde/api/serde.html">API Reference</a> | <a href="https://github.com/yukinarit/pyserde/tree/main/examples">Examples</a>
+  <a href="https://yukinarit.github.io/pyserde/guide/en">Guide</a> | <a href="https://yukinarit.github.io/pyserde/api/serde.html">API Docs</a> | <a href="https://github.com/yukinarit/pyserde/tree/main/examples">Examples</a>
 </p>
 
 ## Overview
diff --git a/docs/en/book.toml b/docs/en/book.toml
index 476a541..49fa836 100644
--- a/docs/en/book.toml
+++ b/docs/en/book.toml
@@ -7,4 +7,4 @@ title = "pyserde documentation"
 
 [output.html]
 git-repository-url = "https://github.com/yukinarit/pyserde"
-edit-url-template = "https://github.com/yukinarit/pyserde/edit/main/docs/en/{path}"
+edit-url-template = "https://github.com/yukinarit/pyserde/edit/main/{path}"
diff --git a/docs/en/introduction.md b/docs/en/introduction.md
index a6d1843..6beea27 100644
--- a/docs/en/introduction.md
+++ b/docs/en/introduction.md
@@ -1,12 +1,8 @@
 # Introduction
 
-`pyserde` is a simple yet powerful serialization library on top of [dataclasses](https://docs.python.org/3/library/dataclasses.html). It allows you to convert Python objects to and from JSON, YAML, and other formats easily and efficiently.
-
-Declare your class with `@serde` decorator and annotate fields using [PEP484](https://peps.python.org/pep-0484/) as below.
+`pyserde` is a simple yet powerful serialization library on top of [dataclasses](https://docs.python.org/3/library/dataclasses.html). Simply adding pyserde's `@serde` decorator makes your class (de)serializable from/to many data formats.
 
 ```python
-from serde import serde
-
 @serde
 class Foo:
     i: int
@@ -18,21 +14,12 @@ class Foo:
 You can serialize `Foo` object into JSON.
 
 ```python
->>> from serde.json import to_json
 >>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
 '{"i":10,"s":"foo","f":100.0,"b":true}'
 ```
 
 You can deserialize JSON into `Foo` object.
 ```python
->>> from serde.json import from_json
 >>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
 Foo(i=10, s='foo', f=100.0, b=True)
 ```
-
-## Next Steps
-
-* [Getting started](getting-started.md)
-* [API Reference](https://yukinarit.github.io/pyserde/api/serde.html)
-* [Examples](https://github.com/yukinarit/pyserde/tree/main/examples)
-* [FAQ](faq.md)
diff --git a/docs/ja/SUMMARY.md b/docs/ja/SUMMARY.md
deleted file mode 100644
index 9fba753..0000000
--- a/docs/ja/SUMMARY.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Summary
-
-- [Introduction](introduction.md)
-- [Getting Started](getting-started.md)
-- [Data Formats](data-formats.md)
-- [Types](types.md)
-- [Decorators](decorators.md)
-- [Class Attributes](class-attributes.md)
-- [Field Attributes](field-attributes.md)
-- [Union](union.md)
-- [Type Checking](type-check.md)
-- [Extension](extension.md)
-- [FAQ](faq.md)
diff --git a/docs/ja/book.toml b/docs/ja/book.toml
deleted file mode 100644
index cd0b57f..0000000
--- a/docs/ja/book.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[book]
-authors = ["yukinarit"]
-language = "ja"
-multilingual = true
-src = "./"
-title = "pyserde documentation"
-
-[output.html]
-git-repository-url = "https://github.com/yukinarit/pyserde"
-edit-url-template = "https://github.com/yukinarit/pyserde/edit/main/docs/ja/{path}"
diff --git a/docs/ja/class-attributes.md b/docs/ja/class-attributes.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/data-formats.md b/docs/ja/data-formats.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/decorators.md b/docs/ja/decorators.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/extension.md b/docs/ja/extension.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/faq.md b/docs/ja/faq.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/field-attributes.md b/docs/ja/field-attributes.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/introduction.md b/docs/ja/introduction.md
deleted file mode 100644
index 908345d..0000000
--- a/docs/ja/introduction.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Introduction
-
-`pyserde`は[dataclasses](https://docs.python.org/3/library/dataclasses.html)ベースのシンプルで強力なシリアライゼーションライブラリで、クラスをJSONやYAML等の様々なデータフォーマットに簡単に効率的に変換可能になります。
-
-クラスに`@serde`デコレータを付け、[PEP484](https://peps.python.org/pep-0484/)形式でフィールドに型アノテーションを付けます。
-
-```python
-from serde import serde
-
-@serde
-class Foo:
-    i: int
-    s: str
-    f: float
-    b: bool
-```
-
-すると、`Foo`クラスは以下のようにJSONにシリアライズ出来るようになります。
-
-```python
->>> from serde.json import to_json
->>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
-'{"i":10,"s":"foo","f":100.0,"b":true}'
-```
-
-また、JSONから`Foo`クラスにデシリアライズも出来るようになります。
-```python
->>> from serde.json import from_json
->>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
-Foo(i=10, s='foo', f=100.0, b=True)
-```
-
-## Next Steps
-
-* [Getting started](getting-started.md)
-* [API Reference](https://yukinarit.github.io/pyserde/api/serde.html)
-* [Examples](https://github.com/yukinarit/pyserde/tree/main/examples)
-* [FAQ](faq.md)
diff --git a/docs/ja/type-check.md b/docs/ja/type-check.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/types.md b/docs/ja/types.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/ja/union.md b/docs/ja/union.md
deleted file mode 100644
index e69de29..0000000
diff --git a/serde/compat.py b/serde/compat.py
index b94c9df..5a78a47 100644
--- a/serde/compat.py
+++ b/serde/compat.py
@@ -513,6 +513,25 @@ def is_bare_opt(typ: Any) -> bool:
     return not type_args(typ) and typ is Optional
 
 
+@cache
+def is_opt_dataclass(typ: Any) -> bool:
+    """
+    Test if the type is optional dataclass.
+
+    >>> is_opt_dataclass(Optional[int])
+    False
+    >>> @dataclasses.dataclass
+    ... class Foo:
+    ...     pass
+    >>> is_opt_dataclass(Foo)
+    False
+    >>> is_opt_dataclass(Optional[Foo])
+    False
+    """
+    args = get_args(typ)
+    return is_opt(typ) and len(args) > 0 and is_dataclass(args[0])
+
+
 @cache
 def is_list(typ: type[Any]) -> bool:
     """
diff --git a/serde/core.py b/serde/core.py
index 0c15d50..aed1301 100644
--- a/serde/core.py
+++ b/serde/core.py
@@ -41,6 +41,7 @@ from .compat import (
     is_new_type_primitive,
     is_any,
     is_opt,
+    is_opt_dataclass,
     is_set,
     is_tuple,
     is_union,
@@ -620,6 +621,8 @@ class Field(Generic[T]):
         flatten = f.metadata.get("serde_flatten")
         if flatten is True:
             flatten = FlattenOpts()
+        if flatten and not (dataclasses.is_dataclass(f.type) or is_opt_dataclass(f.type)):
+            raise SerdeError(f"pyserde does not support flatten attribute for {typename(f.type)}")
 
         kw_only = bool(f.kw_only) if sys.version_info >= (3, 10) else False
 


Test patch:
diff --git a/tests/test_flatten.py b/tests/test_flatten.py
index afcf99b..85c0b39 100644
--- a/tests/test_flatten.py
+++ b/tests/test_flatten.py
@@ -6,7 +6,7 @@ from typing import Any, Optional
 
 import pytest
 
-from serde import field, serde
+from serde import field, serde, SerdeError
 from serde.json import from_json, to_json
 
 from .common import all_formats
@@ -68,3 +68,16 @@ def test_flatten_optional(se: Any, de: Any) -> None:
 
     f = Foo(a=10, b="foo", bar=Bar(c=100.0, d=True))
     assert de(Foo, se(f)) == f
+
+
+@pytest.mark.parametrize("se,de", all_formats)
+def test_flatten_not_supported(se: Any, de: Any) -> None:
+    @serde
+    class Bar:
+        pass
+
+    with pytest.raises(SerdeError):
+
+        @serde
+        class Foo:
+            bar: list[Bar] = field(flatten=True)