Cornices/cornice

[Feature request] It would be nice if one could pass marshmallow's keyword arguments when creating a schema object.

Closed this issue · 2 comments

Hi all,

When using the marshmallow_body_validator (or its siblings), one has to provide a class object (of a schema) rather than an instance. That's understandable, but it limits its usage within views.

For example, if someone wants to use many=True, as @ergo suggested in IRC, one could subclass the original class and pass many=True through its constructor, hence something like this:

class MyCustomManySchema(MySchema):
    def __init__(self, *args, **kwargs):
        kwargs['many'] = True
        Schema.__init__(self, *args, **kwargs)

which is a cool workaround, but adds more classes, boilerplate, complexity, etc.

Moreover, all other marsmallow nifty features that are added through it's constructor (like only, partial, etc) have to be treated accordingly (if applicable).

I tried changing _marshmallow.py validator to add one more dict argument that could have been used to store the keyword arguments when instantiating a schema, but it didn't work (I think it was messing up the meta class defined in _validator()). Here's a patch from what I tried that DIDN'T work:

--- _marshmallow.py	2019-07-19 14:54:15.308854413 +0300
+++ _marshmallow.py.test	2019-07-19 14:46:29.880363619 +0300
@@ -19,7 +19,8 @@
     :rtype: callable
     """
 
-    def _validator(request, schema=None, deserializer=None, **kwargs):
+    def _validator(request, schema=None, deserializer=None, schema_kwargs={},
+                   **kwargs):
         """
         Validate the location against the schema defined on the service.
 
@@ -46,7 +47,7 @@
         if schema is None:
             return
 
-        schema = _instantiate_schema(schema)
+        schema = _instantiate_schema(schema, schema_kwargs)
 
         class ValidatedField(marshmallow.fields.Field):
             def _deserialize(self, value, attr, data):
@@ -96,7 +97,8 @@
             """A schema to validate the request's location attributes."""
             pass
 
-        validator(request, RequestSchema, deserializer, **kwargs)
+        validator(request, RequestSchema, deserializer, schema_kwargs,
+                  **kwargs)
         request.validated = request.validated.get(location, {})
 
     return _validator
@@ -130,7 +132,8 @@
     return dict((name, exc.messages) for name in exc.field_names)
 
 
-def validator(request, schema=None, deserializer=None, **kwargs):
+def validator(request, schema=None, deserializer=None, schema_kwargs={},
+              **kwargs):
     """
     Validate the full request against the schema defined on the service.
 
@@ -158,7 +161,7 @@
     if schema is None:
         return
 
-    schema = _instantiate_schema(schema)
+    schema = _instantiate_schema(schema, schema_kwargs)
     schema.context.setdefault('request', request)
 
     cstruct = deserializer(request)
@@ -185,8 +188,9 @@
         request.validated.update(deserialized)
 
 
-def _instantiate_schema(schema):
+def _instantiate_schema(schema, schema_kwargs):
     if not inspect.isclass(schema):
         raise ValueError('You need to pass Marshmallow class instead '
                          'of schema instance')
+    return schema(**schema_kwargs)
     return schema()

I'll try to find some more time to see if I can manage it (because the specific code is a bit difficult for me to understand, so it takes me time to test new things on it), so it would be very nice if anybody had any pointers/suggestions on how to achieve it.

I just submitted a PR that seems to implement the feature request. Any comments/corrections are more than welcome (@ergo, I'd be glad to hear an OK from you too).

It has been implemented (PR #516), so I'm closing it.