-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow exportation of the Marshmallow Schema of a Document #34
Comments
I've worked a bit on this today. Considering a simple class User(Document):
# Say we use user's name as id
id = fields.StrField(required=True, attribute='_id')
age = fields.IntField()
role = fields.StrField(missing='user') If we want to provide a user API, the data flow will be the following:
The trouble is we have 3 places were serialization/deserialization can occur:
Often json world and client world are the same, however when it's not the case (in the example the API provide From my point of view, there should be a clean distinction between Umongo's fields and Marshmallow's fields. Mixing both will works 90% of the time, which means blowing up during a dependency update or a minor change... Hence, the solution I see would be to provide a way to get pure marshmallow version of any umongo field. Another benefit I see of doing this is to get rid of the So here is the idea: class BaseField(ma_fields.Field):
...
def as_marshmallow_field(self, to_mongo=False, params=None):
"""
Return a pure-marshmallow version of this field.
:param to_mongo: If True, field will (de)serialize as mongodb world instead of as oo world
:param params: Additional parameters passed to the mashmallow field
class constructor.
"""
... Depending of
note: As discussed in #36, Another Most umongo fields are just rip-off of marshmallow ones and should not cause any trouble. |
I totally agree about providing pure Marshmallow schema, for all the reasons you pointed, plus another one: I'm trying to use apispec to generate the API doc. To document the type of each attribute, it relies on the type of the field, so I need the field to be a true Marshmallow field. (This does not work in my current patched version as I return umongo fields.) For custom field, I should extend the field -> type mapping but keep the logic, thus I like the idea of providing custom fields as regular Marshmallow fields first. |
I've done some big work on this feature, see branch https://github.com/Scille/umongo/tree/export_schema and 0a35ce841 Basically I provide Two points: An It's the same thing with the tricky fields (like A parameter @lafrech Can you play a bit with this (see the |
Great! I've got a few things to do before, but I'll test this as soon as I can on my own use case, as a replacement for my export attempt, to see how it goes. |
Since I was pretty happy with the way I implemented this feature, I've corrected the examples and document and merged it into master 😃 Regarding the serialization of missing fields trouble, I've added a Same thing with the @lafrech reopen if you think I've missed something 👍 |
I rebased my patched branch to latest master and tested the new schema export feature. First, I should say that I'm pretty happy with the result. My code is simpler and clearer. I have two points to raise: check_unknow_fieldsI still face #18, for the same reason as before. webargs sets missing_accessorTo be honest, I don't really understand the purpose of I have a problematic use case. Consider a Document with a property: @db_instance.register
class User(Document):
@property
def whatever(self):
return random stuff Let's say we export the Marshmallow Schema and add a field to dump this property: user_schema = User.schema.as_marshmallow_schema()
class UserSchema(user_schema):
class Meta:
strict = True
whatever = ma.fields.String(
dump_only=True
) Then this line fails:
but replacing it with
would defeat the point of I'm not sure what to do about this. I don't think adding properties to a Currently, as a workaround, I disabled the feature:
|
(I'm afraid I can't reopen the issue since you're the one who closed it.) |
To access attributes (see https://github.com/marshmallow-code/marshmallow/blob/dev/marshmallow/utils.py#L317), by default marshmallow will first try to get by item ( Applied to a umongo document, you try first to get the field data, then try to get a function/property defined in the class. Back to your example: @db_instance.register
class User(Document):
a = fields.IntField()
@property
def whatever(self):
return None
user_schema = User.schema.as_marshmallow_schema()
class UserSchema(user_schema):
class Meta:
strict = True
a = ma.fields.Int()
whatever = ma.fields.String(
dump_only=True
)
UserSchema().dump(User())
# Both 'whatever' and 'a' return 'None', however custom accessor can determine
# 'a' real value is 'missing'
# {'whatever': None} So there should be no trouble whit this... |
Thanks, it is clearer now. However, it seems your example does not work. In the tests, the property returns "I'm a property". If we change this to let it return ret = MaSchema.get_attribute(self, attr, obj, default)
# Here, ret is None because that's what the property returns
if ret is None and ret is not default:
# We're going in there, and we get a KeyError
raw_ret = obj._data.get(attr)
return default if raw_ret is missing else raw_ret
else:
return ret Possible fix: ret = MaSchema.get_attribute(self, attr, obj, default)
if ret is None and ret is not default:
try:
raw_ret = obj._data.get(attr, to_raise=KeyError) # Ask for a KeyError explicitly in case the default changes?
except KeyError:
# We got None and attr is not in _fields, let's assume None was really a None
return ret # Or equivalently return None
return default if raw_ret is missing else raw_ret
else:
return ret I just realized that missing fields are not serialized (not in the json) instead of being written as None/null and I remember this is what I wanted. I guess that's what you're referring to when you write "that was your trouble in the first place" and indeed, I think it is nicer this way. Thanks. |
fixed 6c406d6 |
Great. Your fix is much better than my hack. |
Currently, the Schema of a Document can be obtained from Document.Schema. However, this Schema is made to [|de]serialize between "DB / data_proxy" and "OO world", not between "OO world" and "client/JSON world". (See diagram in the docs).
In other words, uMongo is made to be used like this:
The difference being that the data_proxy does not behave like the document:
None
if a key is missingTherefore, using the Schema to serialize a Document may work but it currently has corner cases.
@touilleMan confirms that the ability to export a Marshmallow Schema without the uMongo specificities is in the scope of uMongo and is a feature we want to have.
The idea could be to add a method or attribute to the document to provide that "cleaned up" Schema.
I'm opening this issue to centralize the reflexions about that.
Currently, here are the issues I found:
check_unknown_fields
raises ValidationError if passed adump_only
field even if value ismissing
, which is an issue when validating a document before deserialization. (Bug report: check_unknown_fields raises ValidationError if passed a dump_only field even if value is 'missing' #18, PR: Ignore dump_only fields with missing value in check_unknown_fields #19)None
. Maybe this one is unrelated but just happened to occur while calling `schema.dump(document) (PR: fields: _serialize to None if value is None #32)None
will fail because the Schema tries to find the value in "attribute", while in the document, it is available at the field's name. To avoid this, we could set "attribute" toNone
in all the fields. (PR: Export Marshmallow Schema as Document.DocSchema #33)#33 drafts a way of exporting the Marshmallow Schema from the document.
The text was updated successfully, but these errors were encountered: