Repository: marshmallow-code/marshmallow
Pull request #1466 (2019-12-05 19:37:15)

Make Number._format_num return typing.Any
This allows a subclass that overrides/slash extends the method to set a more specific return type annotation.


Patch:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 00d91f28..073feddb 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -140,3 +140,4 @@ Contributors (chronological)
 - `@phrfpeixoto <https://github.com/phrfpeixoto>`_
 - `@jceresini <https://github.com/jceresini>`_
 - Nikolay Shebanov `@killthekitten <https://github.com/killthekitten>`_
+- Taneli Hukkinen `@hukkinj1 <https://github.com/hukkinj1>`_
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 15993b2c..e1b2aa1b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,12 +1,67 @@
 Changelog
 ---------
 
+3.3.0 (unreleased)
+******************
+
+Features:
+
+- ``fields.Nested`` may take a callable that returns a schema instance.
+  Use this to resolve order-of-declaration issues when schemas nest each other (:issue:`1146`).
+
+.. code-block:: python
+
+    # <3.3
+    class AlbumSchema(Schema):
+        title = fields.Str()
+        artist = fields.Nested("ArtistSchema", only=("name",))
+
+
+    class ArtistSchema(Schema):
+        name = fields.Str()
+        albums = fields.List(fields.Nested(AlbumSchema))
+
+
+    # >=3.3
+    class AlbumSchema(Schema):
+        title = fields.Str()
+        artist = fields.Nested(lambda: ArtistSchema(only=("name",)))
+
+
+    class ArtistSchema(Schema):
+        name = fields.Str()
+        albums = fields.List(fields.Nested(AlbumSchema))
+
+Deprecations:
+
+- Passing the string ``"self"`` to ``fields.Nested`` is deprecated.
+  Use a callable instead.
+
+.. code-block:: python
+
+    from marshmallow import Schema, fields
+
+    # <3.3
+    class PersonSchema(Schema):
+        partner = fields.Nested("self", exclude=("partner",))
+        friends = fields.List(fields.Nested("self"))
+
+
+    # >=3.3
+    class PersonSchema(Schema):
+        partner = fields.Nested(lambda: PersonSchema(exclude=("partner")))
+        friends = fields.List(fields.Nested(lambda: PersonSchema()))
+
+Other changes:
+
+- Make mypy stricter and remove dead code (:pr:`1467`). Thanks :user:`hukkinj1`.
+
 3.2.2 (2019-11-04)
 ******************
 
 Bug fixes:
 
-- Don't load fields for which ``load_only`` and ``dump_only`` are both ``True`` (:pr:`1448`). 
+- Don't load fields for which ``load_only`` and ``dump_only`` are both ``True`` (:pr:`1448`).
 - Fix types in ``marshmallow.validate`` (:pr:`1446`).
 
 Support:
diff --git a/docs/nesting.rst b/docs/nesting.rst
index 66da5df7..6ded19d7 100644
--- a/docs/nesting.rst
+++ b/docs/nesting.rst
@@ -22,7 +22,7 @@ Schemas can be nested to represent relationships between objects (e.g. foreign k
             self.title = title
             self.author = author  # A User object
 
-Use a :class:`Nested <marshmallow.fields.Nested>` field to represent the relationship, passing in a nested schema class.
+Use a :class:`Nested <marshmallow.fields.Nested>` field to represent the relationship, passing in a nested schema.
 
 .. code-block:: python
 
@@ -53,24 +53,24 @@ The serialized blog will have the nested user representation.
     #             'created_at': '2014-08-17T14:58:57.600623+00:00'}}
 
 .. note::
-    If the field is a collection of nested objects, you must set ``many=True``.
+    If the field is a collection of nested objects, pass the `Nested <marshmallow.fields.Nested>` field to `List <marshmallow.fields.List>`.
 
     .. code-block:: python
 
-        collaborators = fields.Nested(UserSchema, many=True)
+        collaborators = fields.List(fields.Nested(UserSchema))
 
 .. _specifying-nested-fields:
 
 Specifying Which Fields to Nest
 -------------------------------
 
-You can explicitly specify which attributes of the nested objects you want to serialize with the ``only`` argument.
+You can explicitly specify which attributes of the nested objects you want to (de)serialize with the ``only`` argument to the schema.
 
 .. code-block:: python
 
     class BlogSchema2(Schema):
         title = fields.String()
-        author = fields.Nested(UserSchema, only=["email"])
+        author = fields.Nested(UserSchema(only=("email",)))
 
 
     schema = BlogSchema2()
@@ -81,7 +81,7 @@ You can explicitly specify which attributes of the nested objects you want to se
     #     'author': {'email': u'monty@python.org'}
     # }
 
-You can represent the attributes of deeply nested objects using dot delimiters.
+Dotted paths may be passed to ``only`` and ``exclude`` to specify nested attributes.
 
 .. code-block:: python
 
@@ -89,7 +89,7 @@ You can represent the attributes of deeply nested objects using dot delimiters.
         blog = fields.Nested(BlogSchema2)
 
 
-    schema = SiteSchema(only=["blog.author.email"])
+    schema = SiteSchema(only=("blog.author.email",))
     result = schema.dump(site)
     pprint(result)
     # {
@@ -125,8 +125,6 @@ You can replace nested data with a single value (or flat list of values if ``man
     # }
 
 
-You can also exclude fields by passing in an ``exclude`` list. This argument also allows representing the attributes of deeply nested objects using dot delimiters.
-
 .. _partial-loading:
 
 Partial Loading
@@ -168,27 +166,29 @@ You can specify a subset of the fields to allow partial loading using dot delimi
 Two-way Nesting
 ---------------
 
-If you have two objects that nest each other, you can refer to a nested schema by its class name. This allows you to nest Schemas that have not yet been defined.
-
+If you have two objects that nest each other, you can pass a callable to `Nested <marshmallow.fields.Nested>`.
+This allows you to resolve order-of-declaration issues, such as when one schema nests a schema that is declared below it.
 
-For example, a representation of an ``Author`` model might include the books that have a foreign-key (many-to-one) relationship to it. Correspondingly, a representation of a ``Book`` will include its author representation.
+For example, a representation of an ``Author`` model might include the books that have a many-to-one relationship to it.
+Correspondingly, a representation of a ``Book`` will include its author representation.
 
 .. code-block:: python
 
-    class AuthorSchema(Schema):
-        # Make sure to use the 'only' or 'exclude' params
+    class BookSchema(Schema):
+        id = fields.Int(dump_only=True)
+        title = fields.Str()
+
+        # Make sure to use the 'only' or 'exclude'
         # to avoid infinite recursion
-        books = fields.Nested("BookSchema", many=True, exclude=("author",))
+        author = fields.Nested(lambda: AuthorSchema(only=("id", "title")))
 
-        class Meta:
-            fields = ("id", "name", "books")
 
+    class AuthorSchema(Schema):
+        id = fields.Int(dump_only=True)
+        title = fields.Str()
 
-    class BookSchema(Schema):
-        author = fields.Nested(AuthorSchema, only=("id", "name"))
+        books = fields.List(fields.Nested(BookSchema(exclude=("author",))))
 
-        class Meta:
-            fields = ("id", "title", "author")
 
 .. code-block:: python
 
@@ -221,27 +221,54 @@ For example, a representation of an ``Author`` model might include the books tha
     #   ]
     # }
 
+You can also pass a class name as a string to `Nested <marshmallow.fields.Nested>`.
+This is useful for avoiding circular imports when your schemas are located in different modules.
+
+.. code-block:: python
+
+    # books.py
+    from marshmallow import Schema, fields
+
+
+    class BookSchema(Schema):
+        id = fields.Int(dump_only=True)
+        title = fields.Str()
+
+        author = fields.Nested("AuthorSchema", only=("id", "title"))
+
+.. code-block:: python
+
+    # authors.py
+    from marshmallow import Schema, fields
+
+
+    class AuthorSchema(Schema):
+        id = fields.Int(dump_only=True)
+        title = fields.Str()
+
+        books = fields.List(fields.Nested("BookSchema", exclude=("author",)))
+
 .. note::
-    If you need to, you can also pass the full, module-qualified path to `fields.Nested`. ::
 
-        books = fields.Nested('path.to.BookSchema',
-                              many=True, exclude=('author', ))
+    If you have multiple schemas with the same class name, you must pass the full, module-qualified path. ::
+
+        author = fields.Nested("authors.BookSchema", only=("id", "title"))
 
 .. _self-nesting:
 
 Nesting A Schema Within Itself
 ------------------------------
 
-If the object to be marshalled has a relationship to an object of the same type, you can nest the `Schema` within itself by passing ``"self"`` (with quotes) to the :class:`Nested <marshmallow.fields.Nested>` constructor.
+If the object to be marshalled has a relationship to an object of the same type, you can nest the `Schema` within itself by passing a callable that returns an instance of the same schema.
 
 .. code-block:: python
 
     class UserSchema(Schema):
         name = fields.String()
         email = fields.Email()
-        friends = fields.Nested("self", many=True)
         # Use the 'exclude' argument to avoid infinite recursion
-        employer = fields.Nested("self", exclude=("employer",), default=None)
+        employer = fields.Nested(lambda: UserSchema(exclude=("employer",)))
+        friends = fields.List(fields.Nested(lambda: UserSchema()))
 
 
     user = User("Steve", "steve@example.com")
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index c01d13a9..ea7d6f9d 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -3,6 +3,55 @@ Upgrading to Newer Releases
 
 This section documents migration paths to new releases.
 
+Upgrading to 3.3
+++++++++++++++++
+
+In 3.3, `fields.Nested <marshmallow.fields.Nested>` may take a callable that returns a schema instance. 
+Use this to resolve order-of-declaration issues when schemas nest each other.
+
+.. code-block:: python
+
+    from marshmallow import Schema, fields
+
+    # <3.3
+    class AlbumSchema(Schema):
+        title = fields.Str()
+        artist = fields.Nested("ArtistSchema", only=("name",))
+
+
+    class ArtistSchema(Schema):
+        name = fields.Str()
+        albums = fields.List(fields.Nested(AlbumSchema))
+
+
+    # >=3.3
+    class AlbumSchema(Schema):
+        title = fields.Str()
+        artist = fields.Nested(lambda: ArtistSchema(only=("name",)))
+
+
+    class ArtistSchema(Schema):
+        name = fields.Str()
+        albums = fields.List(fields.Nested(AlbumSchema))
+
+A callable should also be used when nesting a schema within itself.
+Passing ``"self"`` is deprecated.
+
+.. code-block:: python
+
+    from marshmallow import Schema, fields
+
+    # <3.3
+    class PersonSchema(Schema):
+        partner = fields.Nested("self", exclude=("partner",))
+        friends = fields.List(fields.Nested("self"))
+
+
+    # >=3.3
+    class PersonSchema(Schema):
+        partner = fields.Nested(lambda: PersonSchema(exclude=("partner")))
+        friends = fields.List(fields.Nested(lambda: PersonSchema()))
+
 .. _upgrading_3_0:
 
 Upgrading to 3.0
diff --git a/docs/why.rst b/docs/why.rst
index 08444e30..9f86bbaa 100644
--- a/docs/why.rst
+++ b/docs/why.rst
@@ -33,8 +33,8 @@ As an example, you might have a JSON endpoint for retrieving all information abo
 
     class GameStateSchema(Schema):
         _id = fields.UUID(required=True)
-        players = fields.Nested(PlayerSchema, many=True)
         score = fields.Nested(ScoreSchema)
+        players = fields.List(fields.Nested(PlayerSchema))
         last_changed = fields.DateTime(format="rfc")
 
         class Meta:
diff --git a/examples/peewee_example.py b/examples/peewee_example.py
index a28c4fef..d4da8bf9 100644
--- a/examples/peewee_example.py
+++ b/examples/peewee_example.py
@@ -77,7 +77,7 @@ class UserSchema(Schema):
 class TodoSchema(Schema):
     id = fields.Int(dump_only=True)
     done = fields.Boolean(attribute="is_done", missing=False)
-    user = fields.Nested(UserSchema, exclude=("joined_on", "password"), dump_only=True)
+    user = fields.Nested(UserSchema(exclude=("joined_on", "password")), dump_only=True)
     content = fields.Str(required=True)
     posted_on = fields.DateTime(dump_only=True)
 
diff --git a/setup.cfg b/setup.cfg
index 31093f5a..274e36e3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,3 +16,6 @@ addopts = -v --tb=short
 
 [mypy]
 ignore_missing_imports = true
+warn_unreachable = true
+warn_unused_ignores = true
+warn_redundant_casts = true
diff --git a/src/marshmallow/fields.py b/src/marshmallow/fields.py
index 003c33ae..4458fab1 100644
--- a/src/marshmallow/fields.py
+++ b/src/marshmallow/fields.py
@@ -437,10 +437,19 @@ class Nested(Field):
 
     Examples: ::
 
-        user = fields.Nested(UserSchema)
-        user2 = fields.Nested('UserSchema')  # Equivalent to above
-        collaborators = fields.Nested(UserSchema, many=True, only=('id',))
-        parent = fields.Nested('self')
+        class ChildSchema(Schema):
+            id = fields.Str()
+            name = fields.Str()
+            # Use lambda functions when you need two-way nesting or self-nesting
+            parent = fields.Nested(lambda: ParentSchema(only=("id",)), dump_only=True)
+            siblings = fields.List(fields.Nested(lambda: ChildSchema(only=("id", "name"))))
+
+        class ParentSchema(Schema):
+            id = fields.Str()
+            children = fields.List(
+                fields.Nested(ChildSchema(only=("id", "parent", "siblings")))
+            )
+            spouse = fields.Nested(lambda: ParentSchema(only=("id",)))
 
     When passing a `Schema <marshmallow.Schema>` instance as the first argument,
     the instance's ``exclude``, ``only``, and ``many`` attributes will be respected.
@@ -471,7 +480,7 @@ class Nested(Field):
 
     def __init__(
         self,
-        nested: typing.Union[SchemaABC, type, str],
+        nested: typing.Union[SchemaABC, type, str, typing.Callable[[], SchemaABC]],
         *,
         default: typing.Any = missing_,
         only: types.StrSequenceOrSet = None,
@@ -483,10 +492,16 @@ class Nested(Field):
         # Raise error if only or exclude is passed as string, not list of strings
         if only is not None and not is_collection(only):
             raise StringNotCollectionError('"only" should be a collection of strings.')
-        if exclude is not None and not is_collection(exclude):
+        if not is_collection(exclude):
             raise StringNotCollectionError(
                 '"exclude" should be a collection of strings.'
             )
+        if nested == "self":
+            warnings.warn(
+                "Passing 'self' to `Nested` is deprecated. "
+                "Use `Nested(lambda: MySchema(...))` instead.",
+                DeprecationWarning,
+            )
         self.nested = nested
         self.only = only
         self.exclude = exclude
@@ -505,8 +520,13 @@ class Nested(Field):
         if not self._schema:
             # Inherit context from parent.
             context = getattr(self.parent, "context", {})
-            if isinstance(self.nested, SchemaABC):
-                self._schema = copy.copy(self.nested)
+            if callable(self.nested) and not isinstance(self.nested, type):
+                nested = self.nested()
+            else:
+                nested = self.nested
+
+            if isinstance(nested, SchemaABC):
+                self._schema = copy.copy(nested)
                 self._schema.context.update(context)
                 # Respect only and exclude passed from parent and re-initialize fields
                 set_class = self._schema.set_class
@@ -521,20 +541,17 @@ class Nested(Field):
                     self._schema.exclude = set_class(self.exclude).union(original)
                 self._schema._init_fields()
             else:
-                if isinstance(self.nested, type) and issubclass(self.nested, SchemaABC):
-                    schema_class = self.nested
-                elif not isinstance(self.nested, (str, bytes)):
+                if isinstance(nested, type) and issubclass(nested, SchemaABC):
+                    schema_class = nested
+                elif not isinstance(nested, (str, bytes)):
                     raise ValueError(
-                        "Nested fields must be passed a "
-                        "Schema, not {}.".format(self.nested.__class__)
+                        "`Nested` fields must be passed a "
+                        "`Schema`, not {}.".format(nested.__class__)
                     )
-                elif self.nested == "self":
-                    ret = self
-                    while not isinstance(ret, SchemaABC):
-                        ret = ret.parent
-                    schema_class = ret.__class__
+                elif nested == "self":
+                    schema_class = self.root.__class__
                 else:
-                    schema_class = class_registry.get_class(self.nested)
+                    schema_class = class_registry.get_class(nested)
                 self._schema = schema_class(
                     many=self.many,
                     only=self.only,
@@ -869,7 +886,7 @@ class Number(Field):
         self.as_string = as_string
         super().__init__(**kwargs)
 
-    def _format_num(self, value) -> _T:
+    def _format_num(self, value) -> typing.Any:
         """Return the number value for value, given this field's `num_type`."""
         return self.num_type(value)
 
diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py
index 16867108..2fdd9397 100644
--- a/src/marshmallow/schema.py
+++ b/src/marshmallow/schema.py
@@ -373,7 +373,7 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
         # Raise error if only or exclude is passed as string, not list of strings
         if only is not None and not is_collection(only):
             raise StringNotCollectionError('"only" should be a list of strings')
-        if exclude is not None and not is_collection(exclude):
+        if not is_collection(exclude):
             raise StringNotCollectionError('"exclude" should be a list of strings')
         # copy declared fields from metaclass
         self.declared_fields = copy.deepcopy(self._declared_fields)
@@ -1025,9 +1025,13 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
         try:
             field_obj._bind_to_schema(field_name, self)
         except TypeError as error:
-            # field declared as a class, not an instance
-            if isinstance(field_obj, type) and issubclass(field_obj, base.FieldABC):
-                msg = (
+            # Field declared as a class, not an instance. Ignore type checking because
+            # we handle unsupported arg types, i.e. this is dead code from
+            # the type checker's perspective.
+            if isinstance(field_obj, type) and issubclass(  # type: ignore
+                field_obj, base.FieldABC
+            ):
+                msg = (  # type: ignore
                     'Field for "{}" must be declared as a '
                     "Field instance, not a class. "
                     'Did you mean "fields.{}()"?'.format(field_name, field_obj.__name__)


Test patch:
diff --git a/tests/base.py b/tests/base.py
index 43822166..b2ffa87c 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -275,21 +275,21 @@ class UserRelativeUrlSchema(UserSchema):
 class BlogSchema(Schema):
     title = fields.String()
     user = fields.Nested(UserSchema)
-    collaborators = fields.Nested(UserSchema, many=True)
+    collaborators = fields.List(fields.Nested(UserSchema()))
     categories = fields.List(fields.String)
     id = fields.String()
 
 
 class BlogUserMetaSchema(Schema):
     user = fields.Nested(UserMetaSchema())
-    collaborators = fields.Nested(UserMetaSchema, many=True)
+    collaborators = fields.List(fields.Nested(UserMetaSchema()))
 
 
 class BlogSchemaMeta(Schema):
     """Same as BlogSerializer but using ``fields`` options."""
 
     user = fields.Nested(UserSchema)
-    collaborators = fields.Nested(UserSchema, many=True)
+    collaborators = fields.List(fields.Nested(UserSchema()))
 
     class Meta:
         fields = ("title", "user", "collaborators", "categories", "id")
@@ -298,7 +298,7 @@ class BlogSchemaMeta(Schema):
 class BlogOnlySchema(Schema):
     title = fields.String()
     user = fields.Nested(UserSchema)
-    collaborators = fields.Nested(UserSchema, only=("id",), many=True)
+    collaborators = fields.List(fields.Nested(UserSchema(only=("id",))))
 
 
 class BlogSchemaExclude(BlogSchema):
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 846e1611..8671be95 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -377,6 +377,34 @@ class TestListNested:
             "children": [{"name": "Lily"}]
         }
 
+    @pytest.mark.parametrize(
+        ("param", "expected_attribute", "expected_dump"),
+        (
+            ("only", {"name"}, {"children": [{"name": "Lily"}]}),
+            ("exclude", {"name", "surname", "age"}, {"children": [{}]}),
+        ),
+    )
+    def test_list_nested_lambda_only_and_exclude_merged_with_nested(
+        self, param, expected_attribute, expected_dump
+    ):
+        class Child(Schema):
+            name = fields.String()
+            surname = fields.String()
+            age = fields.Integer()
+
+        class Family(Schema):
+            children = fields.List(
+                fields.Nested(lambda: Child(**{param: ("name", "surname")}))
+            )
+
+        schema = Family(**{param: ["children.name", "children.age"]})
+        assert (
+            getattr(schema.fields["children"].inner.schema, param) == expected_attribute
+        )
+
+        family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
+        assert schema.dump(family) == expected_dump
+
     def test_list_nested_partial_propagated_to_nested(self):
         class Child(Schema):
             name = fields.String(required=True)
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 2be35b77..5918e56e 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1136,11 +1136,9 @@ def test_meta_nested_exclude():
 
 
 def test_nested_custom_set_not_implementing_getitem():
-    """
-    This test checks that Marshmallow can serialize implementations of
-    :mod:`collections.abc.MutableSequence`, with ``__getitem__`` arguments
-    that are not integers.
-    """
+    # This test checks that marshmallow can serialize implementations of
+    # :mod:`collections.abc.MutableSequence`, with ``__getitem__`` arguments
+    # that are not integers.
 
     class ListLikeParent:
         """
@@ -1226,6 +1224,55 @@ def test_deeply_nested_only_and_exclude():
     assert "bah" not in grand_child
 
 
+def test_nested_lambda():
+    class ChildSchema(Schema):
+        id = fields.Str()
+        name = fields.Str()
+        parent = fields.Nested(lambda: ParentSchema(only=("id",)), dump_only=True)
+        siblings = fields.List(fields.Nested(lambda: ChildSchema(only=("id", "name"))))
+
+    class ParentSchema(Schema):
+        id = fields.Str()
+        spouse = fields.Nested(lambda: ParentSchema(only=("id",)))
+        children = fields.List(
+            fields.Nested(lambda: ChildSchema(only=("id", "parent", "siblings")))
+        )
+
+    sch = ParentSchema()
+    data_to_load = {
+        "id": "p1",
+        "spouse": {"id": "p2"},
+        "children": [{"id": "c1", "siblings": [{"id": "c2", "name": "sis"}]}],
+    }
+    loaded = sch.load(data_to_load)
+    assert loaded == data_to_load
+
+    data_to_dump = dict(
+        id="p2",
+        spouse=dict(id="p2"),
+        children=[
+            dict(
+                id="c1",
+                name="bar",
+                parent=dict(id="p2"),
+                siblings=[dict(id="c2", name="sis")],
+            )
+        ],
+    )
+    dumped = sch.dump(data_to_dump)
+    assert dumped == {
+        "id": "p2",
+        "spouse": {"id": "p2"},
+        "children": [
+            {
+                "id": "c1",
+                "parent": {"id": "p2"},
+                "siblings": [{"id": "c2", "name": "sis"}],
+            }
+        ],
+    }
+
+
 @pytest.mark.parametrize("data_key", ("f1", "f5", None))
 def test_data_key_collision(data_key):
     class MySchema(Schema):
@@ -1888,17 +1935,6 @@ class TestNestedSchema:
         )
         return blog
 
-        class FlatBlogSchema(Schema):
-            name = fields.String()
-            user = fields.Nested(UserSchema, only="name")
-            collaborators = fields.Nested(UserSchema, only="name", many=True)
-
-        s = FlatBlogSchema()
-        data = s.dump(blog)
-        assert data["user"] == blog.user.name
-        for i, name in enumerate(data["collaborators"]):
-            assert name == blog.collaborators[i].name
-
     # regression test for https://github.com/marshmallow-code/marshmallow/issues/64
     def test_nested_many_with_missing_attribute(self, user):
         class SimpleBlogSchema(Schema):
@@ -2163,13 +2199,15 @@ class TestSelfReference:
     def user(self, employer):
         return User(name="Tom", employer=employer, age=28)
 
-    def test_nesting_schema_within_itself(self, user, employer):
-        class SelfSchema(Schema):
-            name = fields.String()
-            age = fields.Integer()
-            employer = fields.Nested("self", exclude=("employer",))
+    def test_nesting_schema_by_passing_lambda(self, user, employer):
+        class SelfReferencingSchema(Schema):
+            name = fields.Str()
+            age = fields.Int()
+            employer = fields.Nested(
+                lambda: SelfReferencingSchema(exclude=("employer",))
+            )
 
-        data = SelfSchema().dump(user)
+        data = SelfReferencingSchema().dump(user)
         assert data["name"] == user.name
         assert data["employer"]["name"] == employer.name
         assert data["employer"]["age"] == employer.age
@@ -2185,9 +2223,24 @@ class TestSelfReference:
         assert data["employer"]["name"] == employer.name
         assert data["employer"]["age"] == employer.age
 
+    def test_nesting_schema_self_string(self, user, employer):
+        with pytest.warns(
+            DeprecationWarning, match="Passing 'self' to `Nested` is deprecated"
+        ):
+
+            class SelfSchema(Schema):
+                name = fields.String()
+                age = fields.Integer()
+                employer = fields.Nested("self", exclude=("employer",))
+
+        data = SelfSchema().dump(user)
+        assert data["name"] == user.name
+        assert data["employer"]["name"] == employer.name
+        assert data["employer"]["age"] == employer.age
+
     def test_nesting_within_itself_meta(self, user, employer):
         class SelfSchema(Schema):
-            employer = fields.Nested("self", exclude=("employer",))
+            employer = fields.Nested(lambda: SelfSchema(exclude=("employer",)))
 
             class Meta:
                 additional = ("name", "age")
@@ -2200,7 +2253,7 @@ class TestSelfReference:
 
     def test_nested_self_with_only_param(self, user, employer):
         class SelfSchema(Schema):
-            employer = fields.Nested("self", only=("name",))
+            employer = fields.Nested(lambda: SelfSchema(only=("name",)))
 
             class Meta:
                 fields = ("name", "employer")
@@ -2210,10 +2263,14 @@ class TestSelfReference:
         assert data["employer"]["name"] == employer.name
         assert "age" not in data["employer"]
 
-    def test_multiple_pluck_self_field(self, user):
+    def test_multiple_pluck_self_lambda(self, user):
         class MultipleSelfSchema(Schema):
-            emp = fields.Pluck("self", "name", attribute="employer")
-            rels = fields.Pluck("self", "name", many=True, attribute="relatives")
+            emp = fields.Pluck(
+                lambda: MultipleSelfSchema(), "name", attribute="employer"
+            )
+            rels = fields.Pluck(
+                lambda: MultipleSelfSchema(), "name", many=True, attribute="relatives"
+            )
 
             class Meta:
                 fields = ("name", "emp", "rels")
@@ -2225,9 +2282,28 @@ class TestSelfReference:
         relative = data["rels"][0]
         assert relative == user.relatives[0].name
 
-    def test_nested_self_many(self):
+    def test_multiple_pluck_self_string(self, user):
+        with pytest.warns(
+            DeprecationWarning, match="Passing 'self' to `Nested` is deprecated"
+        ):
+
+            class MultipleSelfSchema(Schema):
+                emp = fields.Pluck("self", "name", attribute="employer")
+                rels = fields.Pluck("self", "name", many=True, attribute="relatives")
+
+                class Meta:
+                    fields = ("name", "emp", "rels")
+
+        schema = MultipleSelfSchema()
+        user.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)]
+        data = schema.dump(user)
+        assert len(data["rels"]) == len(user.relatives)
+        relative = data["rels"][0]
+        assert relative == user.relatives[0].name
+
+    def test_nested_self_many_lambda(self):
         class SelfManySchema(Schema):
-            relatives = fields.Nested("self", many=True)
+            relatives = fields.Nested(lambda: SelfManySchema(), many=True)
 
             class Meta:
                 additional = ("name", "age")
@@ -2240,9 +2316,28 @@ class TestSelfReference:
         assert data["relatives"][0]["name"] == person.relatives[0].name
         assert data["relatives"][0]["age"] == person.relatives[0].age
 
+    def test_nested_self_many_string(self):
+        with pytest.warns(
+            DeprecationWarning, match="Passing 'self' to `Nested` is deprecated"
+        ):
+
+            class SelfManySchema(Schema):
+                relatives = fields.Nested("self", many=True)
+
+                class Meta:
+                    additional = ("name", "age")
+
+        person = User(name="Foo")
+        person.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)]
+        data = SelfManySchema().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
+
     def test_nested_self_list(self):
         class SelfListSchema(Schema):
-            relatives = fields.List(fields.Nested("self"))
+            relatives = fields.List(fields.Nested(lambda: SelfListSchema()))
 
             class Meta:
                 additional = ("name", "age")
@@ -2255,6 +2350,25 @@ 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_string(self):
+        with pytest.warns(
+            DeprecationWarning, match="Passing 'self' to `Nested` is deprecated"
+        ):
+
+            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)