Repository: marshmallow-code/apispec
Pull request #692 (2021-07-08 07:57:03)

Replace missing/default with {load,dump}_default
This PR drops support for versions of marshmallow before 3.13.

Since marshmallow is optional, there is no hard dependency in setup.py to force >=3.13. I added a line to the `EXTRA_REQUIRES` to make things more obvious. 

```py
    "marshmallow": ["marshmallow>=3.13.0"],
```

If someone has a better idea, I'm open to suggestions.

I also updated a docstring to 3.13.0 so that it appears in the docs. The docstring was outdated as it read `2.15.2`.


Patch:
diff --git a/setup.py b/setup.py
index 3301b32..eb2005e 100644
--- a/setup.py
+++ b/setup.py
@@ -2,11 +2,12 @@ import re
 from setuptools import setup, find_packages
 
 EXTRAS_REQUIRE = {
+    "marshmallow": ["marshmallow>=3.13.0"],
     "yaml": ["PyYAML>=3.10"],
     "validation": ["prance[osv]>=0.11"],
     "lint": ["flake8==3.9.2", "flake8-bugbear==21.4.3", "pre-commit~=2.4"],
     "docs": [
-        "marshmallow>=3.0.0",
+        "marshmallow>=3.13.0",
         "pyyaml==5.4.1",
         "sphinx==4.1.1",
         "sphinx-issues==1.2.0",
@@ -16,7 +17,7 @@ EXTRAS_REQUIRE = {
 EXTRAS_REQUIRE["tests"] = (
     EXTRAS_REQUIRE["yaml"]
     + EXTRAS_REQUIRE["validation"]
-    + ["marshmallow>=3.10.0", "pytest", "mock"]
+    + ["marshmallow>=3.13.0", "pytest", "mock"]
 )
 EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["lint"] + ["tox"]
 
diff --git a/src/apispec/ext/marshmallow/__init__.py b/src/apispec/ext/marshmallow/__init__.py
index 4136dbd..9c3326b 100644
--- a/src/apispec/ext/marshmallow/__init__.py
+++ b/src/apispec/ext/marshmallow/__init__.py
@@ -5,7 +5,7 @@
 (for response and headers schemas) and
 `spec.path <apispec.APISpec.path>` (for responses and response headers).
 
-Requires marshmallow>=2.15.2.
+Requires marshmallow>=3.13.0.
 
 ``MarshmallowPlugin`` maps marshmallow ``Field`` classes with OpenAPI types and
 formats.
@@ -51,7 +51,7 @@ with `"x-"` (vendor extension).
         name = fields.Str(metadata={"description": "The user's name"})
         created = fields.DateTime(
             dump_only=True,
-            default=dt.datetime.utcnow,
+            dump_default=dt.datetime.utcnow,
             metadata={"doc_default": "The current datetime"}
         )
 
diff --git a/src/apispec/ext/marshmallow/field_converter.py b/src/apispec/ext/marshmallow/field_converter.py
index 7db713a..dba2913 100644
--- a/src/apispec/ext/marshmallow/field_converter.py
+++ b/src/apispec/ext/marshmallow/field_converter.py
@@ -213,7 +213,7 @@ class FieldConverterMixin:
         if "doc_default" in field.metadata:
             ret["default"] = field.metadata["doc_default"]
         else:
-            default = field.missing
+            default = field.load_default
             if default is not marshmallow.missing and not callable(default):
                 default = field._serialize(default, None, None)
                 ret["default"] = default


Test patch:
diff --git a/tests/schemas.py b/tests/schemas.py
index 729e501..6343ebb 100644
--- a/tests/schemas.py
+++ b/tests/schemas.py
@@ -58,13 +58,13 @@ class OrderedSchema(Schema):
 
 
 class DefaultValuesSchema(Schema):
-    number_auto_default = fields.Int(missing=12)
-    number_manual_default = fields.Int(missing=12, metadata={"doc_default": 42})
-    string_callable_default = fields.Str(missing=lambda: "Callable")
+    number_auto_default = fields.Int(load_default=12)
+    number_manual_default = fields.Int(load_default=12, metadata={"doc_default": 42})
+    string_callable_default = fields.Str(load_default=lambda: "Callable")
     string_manual_default = fields.Str(
-        missing=lambda: "Callable", metadata={"doc_default": "Manual"}
+        load_default=lambda: "Callable", metadata={"doc_default": "Manual"}
     )
-    numbers = fields.List(fields.Int, missing=list)
+    numbers = fields.List(fields.Int, load_default=list)
 
 
 class CategorySchema(Schema):
diff --git a/tests/test_ext_marshmallow_field.py b/tests/test_ext_marshmallow_field.py
index 3569e04..828e87c 100644
--- a/tests/test_ext_marshmallow_field.py
+++ b/tests/test_ext_marshmallow_field.py
@@ -79,26 +79,26 @@ def test_field_with_description(spec_fixture):
     assert res["description"] == "a username"
 
 
-def test_field_with_missing(spec_fixture):
-    field = fields.Str(default="foo", missing="bar")
+def test_field_with_load_default(spec_fixture):
+    field = fields.Str(dump_default="foo", load_default="bar")
     res = spec_fixture.openapi.field2property(field)
     assert res["default"] == "bar"
 
 
-def test_boolean_field_with_false_missing(spec_fixture):
-    field = fields.Boolean(default=None, missing=False)
+def test_boolean_field_with_false_load_default(spec_fixture):
+    field = fields.Boolean(dump_default=None, load_default=False)
     res = spec_fixture.openapi.field2property(field)
     assert res["default"] is False
 
 
-def test_datetime_field_with_missing(spec_fixture):
-    field = fields.Date(missing=dt.date(2014, 7, 18))
+def test_datetime_field_with_load_default(spec_fixture):
+    field = fields.Date(load_default=dt.date(2014, 7, 18))
     res = spec_fixture.openapi.field2property(field)
     assert res["default"] == dt.date(2014, 7, 18).isoformat()
 
 
-def test_field_with_missing_callable(spec_fixture):
-    field = fields.Str(missing=lambda: "dummy")
+def test_field_with_load_default_callable(spec_fixture):
+    field = fields.Str(load_default=lambda: "dummy")
     res = spec_fixture.openapi.field2property(field)
     assert "default" not in res
 
@@ -109,8 +109,8 @@ def test_field_with_doc_default(spec_fixture):
     assert res["default"] == "Manual default"
 
 
-def test_field_with_doc_default_and_missing(spec_fixture):
-    field = fields.Int(missing=12, metadata={"doc_default": 42})
+def test_field_with_doc_default_and_load_default(spec_fixture):
+    field = fields.Int(load_default=12, metadata={"doc_default": 42})
     res = spec_fixture.openapi.field2property(field)
     assert res["default"] == 42
 
@@ -129,7 +129,7 @@ def test_field_with_equal(spec_fixture):
 
 def test_only_allows_valid_properties_in_metadata(spec_fixture):
     field = fields.Str(
-        missing="foo",
+        load_default="foo",
         metadata={
             "description": "foo",
             "not_valid": "lol",
@@ -138,7 +138,7 @@ def test_only_allows_valid_properties_in_metadata(spec_fixture):
         },
     )
     res = spec_fixture.openapi.field2property(field)
-    assert res["default"] == field.missing
+    assert res["default"] == field.load_default
     assert "description" in res
     assert "enum" in res
     assert "allOf" in res
diff --git a/tests/test_ext_marshmallow_openapi.py b/tests/test_ext_marshmallow_openapi.py
index 72ab8c6..83ea7ad 100644
--- a/tests/test_ext_marshmallow_openapi.py
+++ b/tests/test_ext_marshmallow_openapi.py
@@ -11,9 +11,9 @@ from .utils import get_schemas, build_ref
 
 
 class TestMarshmallowFieldToOpenAPI:
-    def test_fields_with_missing_load(self, openapi):
+    def test_fields_with_load_default_load(self, openapi):
         class MySchema(Schema):
-            field = fields.Str(default="foo", missing="bar")
+            field = fields.Str(dump_default="foo", load_default="bar")
 
         res = openapi.schema2parameters(MySchema, location="query")
         if openapi.openapi_version.major < 3: