Repository: marshmallow-code/marshmallow-sqlalchemy

Issue:
Schema deserialisation with defined session and nested field throws an exception
Not sure if this is intended functionality, however the code below throws `ValueError: Deserialization requires a session` when trying to load data into a nested object. 

I would expect passing a session would propagate to nested fields, however this doesn't seem to be the case. Is this by design?

If i remove `followup` (the nested field) from the `only=` property, the data loads fine from the non-nested field.

``` python

class FollowUp(ModelSchema):
    reasonCode =fields.String(attribute='reason_code')
    comment = fields.String()

class Occurence(ModelSchema):
    #... other fields before ...
    followup = fields.Nested(FollowUp())
    status = fields.String()

# Inside my request handler:
obj = DBOccurence.query.get_or_404(occ_id)
res = Occurence(only=['status', 'followup']).load(
            request.json,
            instance=obj,
            session=current_app.db.session
        )
```


Comments:

Patch:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 6119d05..fc44907 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -30,3 +30,4 @@ Contributors
 - jean-philippe serafin `@jeanphix <https://github.com/jeanphix>`_
 - Jack Smith `@jacksmith15 <https://github.com/jacksmith15>`_
 - Kazantcev Andrey `@heckad <https://github.com/heckad>`_
+- Samuel Searles-Bryant `@samueljsb <https://github.com/samueljsb>`_
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c1163bf..71ecaaa 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,52 @@
 Changelog
 ---------
 
+0.17.1 (unreleased)
++++++++++++++++++++
+
+Bug fixes:
+
+* Add ``marshmallow_sqlalchemy.fields.Nested`` field that inherits its session from its schema. This fixes a bug where an exception was raised when using ``Nested`` within a ``ModelSchema`` (:issue:`67`). 
+  Thanks :user:`nickw444` for reporting and thanks :user:`samueljsb` for the PR.
+
+User code should be updated to use marshmallow-sqlalchemy's ``Nested`` instead of ``marshmallow.fields.Nested``.
+
+.. code-block:: python
+
+    # Before
+    from marshmallow import fields
+    from marshmallow_sqlalchemy import ModelSchema
+
+
+    class ArtistSchema(ModelSchema):
+        class Meta:
+            model = models.Artist
+
+
+    class AlbumSchema(ModelSchema):
+        class Meta:
+            model = models.Album
+
+        artist = fields.Nested(ArtistSchema)
+
+
+    # After
+    from marshmallow import fields
+    from marshmallow_sqlalchemy import ModelSchema
+    from marshmallow_sqlalchemy.fields import Nested
+
+
+    class ArtistSchema(ModelSchema):
+        class Meta:
+            model = models.Artist
+
+
+    class AlbumSchema(ModelSchema):
+        class Meta:
+            model = models.Album
+
+        artist = Nested(ArtistSchema)
+
 0.17.0 (2019-06-22)
 +++++++++++++++++++
 
diff --git a/docs/recipes.rst b/docs/recipes.rst
index e717941..61e5b08 100644
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -124,11 +124,12 @@ Any field generated by a `ModelSchema <marshmallow_sqlalchemy.ModelSchema>` can
 
     from marshmallow import fields
     from marshmallow_sqlalchemy import ModelSchema
+    from marshmallow_sqlalchemy.fields import Nested
 
 
     class AuthorSchema(ModelSchema):
         # Override books field to use a nested representation rather than pks
-        books = fields.Nested(BookSchema, many=True, exclude=("author",))
+        books = Nested(BookSchema, many=True, exclude=("author",))
 
         class Meta:
             model = Author
@@ -162,7 +163,7 @@ You can customize the keyword arguments passed to a column property's correspond
 Automatically Generating Schemas For SQLAlchemy Models
 ======================================================
 
-It can be tedious to implement a large number of schemas if not overriding any of the generated fields as detailed above. SQLAlchemy has a hook that can be used to trigger the creation of the schemas, assigning them to the SQLAlchemy model property `<Model.__marshmallow__>`.
+It can be tedious to implement a large number of schemas if not overriding any of the generated fields as detailed above. SQLAlchemy has a hook that can be used to trigger the creation of the schemas, assigning them to the SQLAlchemy model property ``Model.__marshmallow__``.
 
 .. code-block:: python
 
@@ -243,7 +244,10 @@ To serialize nested attributes to primary keys unless they are already loaded, y
 
 .. code-block:: python
 
-    class SmartNested(fields.Nested):
+    from marshmallow_sqlalchemy.fields import Nested
+
+
+    class SmartNested(Nested):
         def serialize(self, attr, obj, accessor=None):
             if attr not in obj.__dict__:
                 return {"id": int(getattr(obj, attr + "_id"))}
diff --git a/src/marshmallow_sqlalchemy/fields.py b/src/marshmallow_sqlalchemy/fields.py
index 8289d08..64d19ac 100644
--- a/src/marshmallow_sqlalchemy/fields.py
+++ b/src/marshmallow_sqlalchemy/fields.py
@@ -131,3 +131,16 @@ class Related(fields.Field):
             if result is None:
                 raise NoResultFound
         return result
+
+
+class Nested(fields.Nested):
+    """Nested field that inherits the session from its parent."""
+
+    def _deserialize(self, *args, **kwargs):
+        if hasattr(self.schema, "session"):
+            try:
+                self.schema.session = self.root.session
+            except AttributeError:
+                # Marshmallow 2.0.0 has no root property.
+                self.schema.session = self.parent.session
+        return super(Nested, self)._deserialize(*args, **kwargs)


Test patch:
diff --git a/tests/test_marshmallow_sqlalchemy.py b/tests/test_marshmallow_sqlalchemy.py
index da5b9d0..cc77550 100644
--- a/tests/test_marshmallow_sqlalchemy.py
+++ b/tests/test_marshmallow_sqlalchemy.py
@@ -25,7 +25,7 @@ from marshmallow_sqlalchemy import (
     field_for,
     ModelConversionError,
 )
-from marshmallow_sqlalchemy.fields import Related, RelatedList
+from marshmallow_sqlalchemy.fields import Related, RelatedList, Nested
 
 MARSHMALLOW_VERSION_INFO = tuple(
     [int(part) for part in marshmallow.__version__.split(".") if part.isdigit()]
@@ -1318,6 +1318,77 @@ class TestMarshmallowContext:
         assert dump_result == data
 
 
+class TestNestedFieldSession:
+    """Test the session can be passed to nested fields."""
+
+    @pytest.fixture
+    def association_table(self, Base):
+        return sa.Table(
+            "association",
+            Base.metadata,
+            sa.Column("left_id", sa.Integer, sa.ForeignKey("left.id")),
+            sa.Column("right_id", sa.Integer, sa.ForeignKey("right.id")),
+        )
+
+    @pytest.fixture
+    def parent_model(self, Base, association_table):
+        class Parent(Base):
+            __tablename__ = "left"
+            id = sa.Column(sa.Integer, primary_key=True)
+            name = sa.Column(sa.Text)
+            children = relationship(
+                "Child", secondary=association_table, back_populates="parents"
+            )
+
+        return Parent
+
+    @pytest.fixture
+    def child_model(self, Base, parent_model, association_table):
+        class Child(Base):
+            __tablename__ = "right"
+            id = sa.Column(sa.Integer, primary_key=True)
+            name = sa.Column(sa.Text)
+            parents = relationship(
+                "Parent", secondary=association_table, back_populates="children"
+            )
+
+        return Child
+
+    @pytest.fixture
+    def child_schema(self, child_model):
+        class ChildSchema(ModelSchema):
+            class Meta:
+                model = child_model
+
+        return ChildSchema
+
+    def test_session_is_passed_to_nested_field(
+        self, child_schema, parent_model, session
+    ):
+        class ParentSchema(ModelSchema):
+            children = Nested(child_schema, many=True)
+
+            class Meta:
+                model = parent_model
+
+        data = {"name": "Parent1", "children": [{"name": "Child1"}]}
+        ParentSchema().load(data, session=session)
+
+    def test_session_is_passed_to_nested_field_in_list_field(
+        self, parent_model, child_model, child_schema, session
+    ):
+        """The session is passed to a nested field in a List field."""
+
+        class ParentSchema(ModelSchema):
+            children = fields.List(Nested(child_schema))
+
+            class Meta:
+                model = parent_model
+
+        data = {"name": "Jorge", "children": [{"name": "Jose"}]}
+        ParentSchema().load(data, session=session)
+
+
 def _repr_validator_list(validators):
     return sorted([repr(validator) for validator in validators])