Comments (12)
@skqr You can use a @post_load method to do the same thing as data_handler
.
Yet another alternative is to update a schema's fields on __init__
.
from marshmallow import Schema, fields
class MySchema(Schema):
def __init__(self, additional_fields=None, **kwargs):
super().__init__(**kwargs)
self.declared_fields.update(additional_fields)
additional_fields = {
'foo': fields.Int()
}
sch = MySchema(additional_fields=additional_fields)
print(sch.dump({'foo': '123'}).data) # {'foo': 123}
from marshmallow.
@MrYoda Here is an example of how to do that (if I understand problem correctly):
import marshmallow as m
import marshmallow.fields as mf
from marshmallow.compat import with_metaclass
from collections import namedtuple, OrderedDict
LANGUAGES = ['en', 'de', 'fr']
class I18NMeta(type):
def __new__(cls, name, bases, attrs):
new_attrs = OrderedDict(attrs)
if 'Meta' in attrs:
for name in getattr(attrs['Meta'], 'i18n_fields', []):
if name not in attrs:
continue
field = attrs[name]
for lang in LANGUAGES:
new_attrs['%s_%s' % (name, lang)] = field
return type(name, bases, new_attrs)
class ModelSchema(with_metaclass(I18NMeta, m.Schema)):
class Meta:
ordered = True
i18n_fields = ['value']
value = mf.String()
Model = namedtuple('Model', ['value', 'value_en', 'value_de', 'value_fr'])
print ModelSchema().dump(Model('Hello', 'Hello', 'Hallo', 'Bonjour'))
# => MarshalResult(data=OrderedDict([(u'value', u'Hello'), (u'value_en', u'Hello'), (u'value_de', u'Hallo'), (u'value_fr', u'Bonjour')]), errors={})
Alternatively, you can use special type to mark localized strings:
import marshmallow as m
import marshmallow.fields as mf
from marshmallow.compat import with_metaclass, iteritems
from collections import namedtuple, OrderedDict
LANGUAGES = ['en', 'de', 'fr']
class LocalizedString(mf.String):
pass
class I18NMeta(type):
def __new__(cls, name, bases, attrs):
new_attrs = OrderedDict(attrs)
for field_name, field in iteritems(attrs):
if not isinstance(field, LocalizedString):
continue
for lang in LANGUAGES:
new_attrs['%s_%s' % (field_name, lang)] = field
return type(name, bases, new_attrs)
class ModelSchema(with_metaclass(I18NMeta, m.Schema)):
value = LocalizedString()
Model = namedtuple('Model', ['value', 'value_en', 'value_de', 'value_fr'])
print ModelSchema().dump(Model('Hello', 'Hello', 'Hallo', 'Bonjour'))
from marshmallow.
I suppose I can check through the list of posts before serializing and add the missing attributes (set to None), but if there is a way to set a global default, that would be awesome.
from marshmallow.
There are a couple of ways to handle your use case. Which one you choose will depend on your desired output.
Let's start with our "model" and a few instances:
class Post(object):
def __init__(self, title):
self.title = title
post_no_categories = Post('A post with no categories')
post_with_categories = Post('A post with categories')
post_with_categories.categories = ['music', 'video']
Option 1: Optional fields
If you want the serialized output to always contain the additional fields and it's fine if they are None
, you can use optional fields, e.g., set required=False
on the fields.
from marshmallow import Serializer, fields, pprint
class PostSerializer(Serializer):
title = fields.String(default='Untitled')
categories = fields.List(fields.String, required=False)
PostSerializer(post_no_categories).data
# {"categories": null, "title": "A post with no categories"}
PostSerializer(post_with_categories).data
# {"categories": ["music", "video"], "title": "A post with categories"}
Option 2: Post-processing function
If you want to only include the additional fields if they are defined on an instance, you could do so with a custom data handler. This is perhaps more flexible than option 1. See the docs on Transforming Data.
class PostSerializer2(Serializer):
title = fields.String(default='Untitled')
additional_fields = {
'categories': fields.List(fields.String())
}
# Register a custom data handler that will add the extra fields
@PostSerializer2.data_handler
def add_additional_fields(serializer, data, obj):
for name, field_obj in additional_fields.items():
if hasattr(obj, name):
data[name] = field_obj.output(name, obj)
return data
PostSerializer2(post_no_categories).data
# {"title": "A post with no categories"}
PostSerializer2(post_with_categories).data
# {"title": "A post with categories", "categories": ["music", "video"]}
Hope that helps!
from marshmallow.
Yes! Option 2 is fantastic. However, I'm finding the data_handlers are really persistent.
I'm creating instances of the PostSerializer on the fly and altering the fields via url arguments (ie. ?include=category) and I notice that the data_handler sticks around.
So if on one query I request /posts?include=category
so that the posts include the category, but on the next query I want to include only the subcategory /posts?include=subcategory
, the category will still persist.
Is there a way to "zero out" the data_handler between requests?
from marshmallow.
It seems as though applying the decorator the new instance of the class passed it to the parent. I'm creating a new child class now and setting the decorators on the child, and that seems to be working much better. Thank you.
from marshmallow.
Glad to hear that worked!
from marshmallow.
And what would we be doing after data_handler
was removed in 2.0, @sloria ?
Thanks!
from marshmallow.
So tired. I can not access original object via post_dump.
My case: I have many models in my django project with various text data, and I going to integrate django-modeltranslation to i18ning my models. And I would not add manually all fields with translations to schemas, but I want to do it on fly depending on settings.LANGUAGES, for example.
Some code:
class InterfaceI18N(models.Model):
label = models.CharField('string id', max_length=150, db_index=True,
# after django-modeltranslation integration this field will have copies for i18n purposes and this model will be extended by fields: value_de, value_en, value_fr and others
value = models.TextField('value', default='')
class Meta:
verbose_name = 'interface string'
verbose_name_plural = 'interface strings'
def __str__(self):
return self.label
class InterfaceI18NSchema(Schema):
label = fields.String()
value = fields.String()
@post_dump
def add_i18n_fields(self, *args, **kwargs):
raise Exception('Can not add all value_XX fields for all languages :(')
What do you think about this case, @sloria ? I can not find any right way to do it avoiding write all i18n fields in schema manually.
from marshmallow.
@MrYoda I would create a custom metaclass that would take a list of I18n field names and creates clones and adds post_dump processors.
Although, unless you're writing a translation tool the whole idea looks weird.
from marshmallow.
@maximkulkin Thank you for very good snippet!
from marshmallow.
Another data point for the record, in case someone else runs into this problem.
I had the same AttributeError: description is not a valid field for <some.Model object>
. For me the problem was that the object had a description
@property
and this method tried to get a related item from a database and get the description from there. When no object was found, this gave an AttributeError
. I fixed it by returning None in that case.
In other words: the real error might be hiding behind the AttributeError
raised by marshmallow
.
from marshmallow.
Related Issues (20)
- Wrong value in __call__() function of validate.Validator HOT 1
- Validation of required nested field is not consistent with previous version
- Create raw list Schema HOT 1
- The function decorated by the "post_load" decorator will undergo changes in parameter types as the function name changes. HOT 1
- Deprecate __version__ and related attributes
- Address deprecation warnings in tests HOT 1
- URL validation fails if URL is missing user field HOT 5
- fields.URL validation error should specify "not a valid FQDN" if trying to pass hostname without domain
- missing `f` for f-Strings HOT 2
- `fields.Url` does not accept `file` URLs
- when a new request, schema context not reset !!! HOT 2
- Questions about Copilot + Open Source Software Hierarchy HOT 3
- Allow None for a allow_none=True Date field HOT 6
- fields.Nested Type Warning Since Version 3.15.0
- Setting many=True through Meta - SchemaOpts HOT 1
- RFE: use `zoneinfo` instead of `pytz` HOT 3
- 3.21.2: pytest fails in units checking time HOT 1
- 3.21.2: sphinx warnings `reference target not found` HOT 2
- Static typing of Field subclass kwargs
- Help understanding how to set Schema meta options when using Schema.from_dict() HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from marshmallow.