Repository: marshmallow-code/marshmallow
Pull request #1294 (2019-07-09 09:53:01)

Fix List(Nested("self"))
Addresses https://github.com/marshmallow-code/marshmallow/issues/779#issuecomment-396354987.
Replaces https://github.com/marshmallow-code/marshmallow/pull/1290.

This PR implements the "climb up to parent schema" described in https://github.com/marshmallow-code/marshmallow/pull/1290#issuecomment-509228845.

The second commit is a minor rework of `Field.root`. It removes a condition that I think is useless.


Patch:
diff --git a/performance/benchmark.py b/performance/benchmark.py
index 6bb2783b..4320b379 100644
--- a/performance/benchmark.py
+++ b/performance/benchmark.py
@@ -9,10 +9,9 @@ import argparse
 import cProfile
 import gc
 import timeit
-import datetime
+import time
 
 from marshmallow import Schema, fields, ValidationError, post_dump
-from marshmallow.utils import UTC
 
 
 # Custom validator
@@ -30,7 +29,10 @@ class AuthorSchema(Schema):
     address = fields.Str()
     full_name = fields.Method("full_name")
 
-    def full_name(self, author):
+    def full_name(self, obj):
+        return obj.first + " " + obj.last
+
+    def format_name(self, author):
         return "{}, {}".format(author.last, author.first)
 
 
@@ -38,7 +40,7 @@ class QuoteSchema(Schema):
     id = fields.Int(dump_only=True)
     author = fields.Nested(AuthorSchema, validate=must_not_be_blank)
     content = fields.Str(required=True, validate=must_not_be_blank)
-    posted_at = fields.DateTime(dump_only=True)
+    posted_at = fields.Int(dump_only=True)
     book_name = fields.Str()
     page_number = fields.Float()
     line_number = fields.Float()
@@ -49,7 +51,6 @@ class QuoteSchema(Schema):
         data["author_full"] = "{}, {}".format(
             data["author"]["last"], data["author"]["first"]
         )
-        return data
 
 
 class Author:
@@ -140,7 +141,7 @@ def main():
                 i,
                 Author(i, "Foo", "Bar", 42, 66, "123 Fake St"),
                 "Hello World",
-                datetime.datetime(2019, 7, 4, tzinfo=UTC),
+                time.time(),
                 "The World",
                 34,
                 3,
diff --git a/setup.py b/setup.py
index 35138e9f..8bed8c72 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
 
 EXTRAS_REQUIRE = {
     "tests": ["pytest", "pytz", "simplejson"],
-    "lint": ["flake8==3.7.8", "flake8-bugbear==19.3.0", "pre-commit~=1.17"],
+    "lint": ["flake8==3.7.7", "flake8-bugbear==19.3.0", "pre-commit~=1.17"],
     "docs": [
         "sphinx==2.1.2",
         "sphinx-issues==1.2.0",
diff --git a/src/marshmallow/fields.py b/src/marshmallow/fields.py
index 85eec373..4afd2619 100644
--- a/src/marshmallow/fields.py
+++ b/src/marshmallow/fields.py
@@ -366,7 +366,7 @@ class Field(FieldABC):
         Return `None` for unbound fields.
         """
         ret = self
-        while hasattr(ret, "parent") and ret.parent:
+        while hasattr(ret, "parent"):
             ret = ret.parent
         return ret if isinstance(ret, SchemaABC) else None
 
@@ -453,7 +453,10 @@ class Nested(Field):
                         "Schema, not {}.".format(self.nested.__class__)
                     )
                 elif self.nested == "self":
-                    schema_class = self.parent.__class__
+                    ret = self
+                    while not isinstance(ret, SchemaABC):
+                        ret = ret.parent
+                    schema_class = ret.__class__
                 else:
                     schema_class = class_registry.get_class(self.nested)
                 self.__schema = schema_class(


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 32deab2b..d41a5933 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -2359,7 +2359,7 @@ class TestSelfReference:
         relative = data["rels"][0]
         assert relative == user.relatives[0].name
 
-    def test_nested_many(self):
+    def test_nested_self_many(self):
         class SelfManySchema(Schema):
             relatives = fields.Nested("self", many=True)
 
@@ -2374,6 +2374,21 @@ class TestSelfReference:
         assert data["relatives"][0]["name"] == person.relatives[0].name
         assert data["relatives"][0]["age"] == person.relatives[0].age
 
+    def test_nested_self_list(self):
+        class SelfListSchema(Schema):
+            relatives = fields.List(fields.Nested("self"))
+
+            class Meta:
+                additional = ("name", "age")
+
+        person = User(name="Foo")
+        person.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)]
+        data = SelfListSchema().dump(person)
+        assert data["name"] == person.name
+        assert len(data["relatives"]) == len(person.relatives)
+        assert data["relatives"][0]["name"] == person.relatives[0].name
+        assert data["relatives"][0]["age"] == person.relatives[0].age
+
 
 class RequiredUserSchema(Schema):
     name = fields.Field(required=True)