Repository: Scille/umongo
Pull request #264 (2020-04-29 21:43:55)

Refactor: put Schema stuff in schema.py


Patch:
diff --git a/umongo/__init__.py b/umongo/__init__.py
index 0b22fc9..9c08528 100644
--- a/umongo/__init__.py
+++ b/umongo/__init__.py
@@ -26,7 +26,7 @@ from .exceptions import (
     UnknownFieldInDBError,
 )
 from . import fields, validate
-from .abstract import BaseSchema
+from .schema import Schema
 from .data_objects import Reference
 from .embedded_document import EmbeddedDocument
 from .i18n import set_gettext
@@ -64,7 +64,7 @@ __all__ = (
 
     'fields',
 
-    'BaseSchema',
+    'Schema',
 
     'Reference',
 
diff --git a/umongo/abstract.py b/umongo/abstract.py
index 2d14e08..fe19d9d 100644
--- a/umongo/abstract.py
+++ b/umongo/abstract.py
@@ -2,7 +2,6 @@ from marshmallow import (Schema as MaSchema, fields as ma_fields,
                          validate as ma_validate, missing)
 
 from .i18n import gettext as _, N_
-from .marshmallow_bonus import schema_from_umongo_get_attribute
 
 
 __all__ = ('BaseSchema', 'BaseField', 'BaseValidator', 'BaseDataObject')
@@ -24,8 +23,6 @@ class BaseSchema(MaSchema):
         super().__init__(*args, **kwargs)
         self.error_messages = I18nErrorDict(self.error_messages)
 
-    _marshmallow_schemas_cache = {}
-
     def map_to_field(self, func):
         """
         Apply a function to every field in the schema
@@ -39,37 +36,6 @@ class BaseSchema(MaSchema):
             if hasattr(field, 'map_to_field'):
                 field.map_to_field(mongo_path, name, func)
 
-    def as_marshmallow_schema(self, *, mongo_world=False):
-        """
-        Return a pure-marshmallow version of this schema class.
-
-        :param mongo_world: If True the schema will work against the mongo world
-            instead of the OO world (default: False).
-        """
-        # Use a cache to avoid generating several times the same schema
-        cache_key = (self.__class__, self.MA_BASE_SCHEMA_CLS, mongo_world)
-        if cache_key in self._marshmallow_schemas_cache:
-            return self._marshmallow_schemas_cache[cache_key]
-
-        # Create schema if not found in cache
-        nmspc = {
-            name: field.as_marshmallow_field(mongo_world=mongo_world)
-            for name, field in self.fields.items()
-        }
-        name = 'Marshmallow%s' % type(self).__name__
-        # By default OO world returns `missing` fields as `None`,
-        # disable this behavior here to let marshmallow deal with it
-        if not mongo_world:
-            nmspc['get_attribute'] = schema_from_umongo_get_attribute
-        m_schema = type(name, (self.MA_BASE_SCHEMA_CLS, ), nmspc)
-        # Add i18n support to the schema
-        # We can't use I18nErrorDict here because __getitem__ is not called
-        # when error_messages is updated with _default_error_messages.
-        m_schema._default_error_messages = {
-            k: _(v) for k, v in m_schema._default_error_messages.items()}
-        self._marshmallow_schemas_cache[cache_key] = m_schema
-        return m_schema
-
 
 class BaseField(ma_fields.Field):
     """
diff --git a/umongo/builder.py b/umongo/builder.py
index 9589697..e2a3c74 100644
--- a/umongo/builder.py
+++ b/umongo/builder.py
@@ -16,7 +16,7 @@ from .document import DocumentTemplate, DocumentOpts, DocumentImplementation
 from .embedded_document import (
     EmbeddedDocumentTemplate, EmbeddedDocumentOpts, EmbeddedDocumentImplementation)
 from .exceptions import DocumentDefinitionError, NotRegisteredDocumentError
-from .abstract import BaseSchema
+from .schema import Schema
 from .indexes import parse_index
 from . import fields
 
@@ -281,7 +281,7 @@ class BaseBuilder:
         schema_bases = tuple([base.Schema for base in bases
                               if hasattr(base, 'Schema')])
         if not schema_bases:
-            schema_bases = (BaseSchema, )
+            schema_bases = (Schema, )
         nmspc['pk_field'] = _on_need_add_id_field(schema_bases, schema_fields)
         # If Document is a child, _cls field must be added to the schema
         if opts.is_child:
@@ -329,7 +329,7 @@ class BaseBuilder:
         schema_bases = tuple([base.Schema for base in bases
                               if hasattr(base, 'Schema')])
         if not schema_bases:
-            schema_bases = (BaseSchema, )
+            schema_bases = (Schema, )
         schema_cls = self._build_schema(template, schema_bases, schema_fields, schema_non_fields)
         nmspc['Schema'] = schema_cls
         schema = schema_cls()
diff --git a/umongo/marshmallow_bonus.py b/umongo/marshmallow_bonus.py
index 0759ee0..441c1a1 100644
--- a/umongo/marshmallow_bonus.py
+++ b/umongo/marshmallow_bonus.py
@@ -1,56 +1,17 @@
+"""Pure marshmallow fields used in umongo"""
 import bson
-from marshmallow import ValidationError, Schema as MaSchema, fields as ma_fields, missing
+from marshmallow import ValidationError, fields as ma_fields
 
 from .i18n import gettext as _
 
 
 __all__ = (
-    'schema_from_umongo_get_attribute',
-    'SchemaFromUmongo',
-
     'ObjectId',
     'Reference',
     'GenericReference'
 )
 
 
-# Bonus: schema helpers !
-
-
-def schema_from_umongo_get_attribute(self, obj, attr, default):
-    """
-    Overwrite default `Schema.get_attribute` method by this one to access
-        umongo missing fields instead of returning `None`.
-
-    example::
-
-        class MySchema(marshsmallow.Schema):
-            get_attribute = schema_from_umongo_get_attribute
-
-            # Define the rest of your schema
-            ...
-
-    """
-    ret = MaSchema.get_attribute(self, obj, attr, default)
-    if ret is None and ret is not default and attr in obj.schema.fields:
-        raw_ret = obj._data.get(attr)
-        return default if raw_ret is missing else raw_ret
-    return ret
-
-
-class SchemaFromUmongo(MaSchema):
-    """
-    Custom :class:`marshmallow.Schema` subclass providing unknown fields
-    checking and custom get_attribute for umongo documents.
-
-    .. note: It is not mandatory to use this schema with umongo document.
-        This is just a helper providing usefull behaviors.
-    """
-    get_attribute = schema_from_umongo_get_attribute
-
-
-# Bonus: new fields !
-
 class ObjectId(ma_fields.Field):
     """
     Marshmallow field for :class:`bson.ObjectId`
diff --git a/umongo/schema.py b/umongo/schema.py
new file mode 100644
index 0000000..bb6485d
--- /dev/null
+++ b/umongo/schema.py
@@ -0,0 +1,81 @@
+"""Schema used in Document"""
+from marshmallow import Schema as MaSchema, missing
+
+from .abstract import BaseSchema
+from .i18n import gettext as _
+
+
+__all__ = (
+    'Schema',
+    'schema_from_umongo_get_attribute',
+    'SchemaFromUmongo',
+)
+
+
+def schema_from_umongo_get_attribute(self, obj, attr, default):
+    """
+    Overwrite default `Schema.get_attribute` method by this one to access
+        umongo missing fields instead of returning `None`.
+
+    example::
+
+        class MySchema(marshsmallow.Schema):
+            get_attribute = schema_from_umongo_get_attribute
+
+            # Define the rest of your schema
+            ...
+
+    """
+    ret = MaSchema.get_attribute(self, obj, attr, default)
+    if ret is None and ret is not default and attr in obj.schema.fields:
+        raw_ret = obj._data.get(attr)
+        return default if raw_ret is missing else raw_ret
+    return ret
+
+
+class SchemaFromUmongo(MaSchema):
+    """
+    Custom :class:`marshmallow.Schema` subclass providing unknown fields
+    checking and custom get_attribute for umongo documents.
+
+    .. note: It is not mandatory to use this schema with umongo document.
+        This is just a helper providing usefull behaviors.
+    """
+    get_attribute = schema_from_umongo_get_attribute
+
+
+class Schema(BaseSchema):
+    """Schema used in Document"""
+
+    _marshmallow_schemas_cache = {}
+
+    def as_marshmallow_schema(self, *, mongo_world=False):
+        """
+        Return a pure-marshmallow version of this schema class.
+
+        :param mongo_world: If True the schema will work against the mongo world
+            instead of the OO world (default: False).
+        """
+        # Use a cache to avoid generating several times the same schema
+        cache_key = (self.__class__, self.MA_BASE_SCHEMA_CLS, mongo_world)
+        if cache_key in self._marshmallow_schemas_cache:
+            return self._marshmallow_schemas_cache[cache_key]
+
+        # Create schema if not found in cache
+        nmspc = {
+            name: field.as_marshmallow_field(mongo_world=mongo_world)
+            for name, field in self.fields.items()
+        }
+        name = 'Marshmallow%s' % type(self).__name__
+        # By default OO world returns `missing` fields as `None`,
+        # disable this behavior here to let marshmallow deal with it
+        if not mongo_world:
+            nmspc['get_attribute'] = schema_from_umongo_get_attribute
+        m_schema = type(name, (self.MA_BASE_SCHEMA_CLS, ), nmspc)
+        # Add i18n support to the schema
+        # We can't use I18nErrorDict here because __getitem__ is not called
+        # when error_messages is updated with _default_error_messages.
+        m_schema._default_error_messages = {
+            k: _(v) for k, v in m_schema._default_error_messages.items()}
+        self._marshmallow_schemas_cache[cache_key] = m_schema
+        return m_schema


Test patch:
diff --git a/tests/test_data_proxy.py b/tests/test_data_proxy.py
index 6c7166f..601f9ee 100644
--- a/tests/test_data_proxy.py
+++ b/tests/test_data_proxy.py
@@ -4,7 +4,7 @@ from bson import ObjectId
 from marshmallow import ValidationError, missing
 
 from umongo.data_proxy import data_proxy_factory, BaseDataProxy, BaseNonStrictDataProxy
-from umongo import BaseSchema, fields, EmbeddedDocument, validate, exceptions
+from umongo import Schema, fields, EmbeddedDocument, validate, exceptions
 
 from .common import BaseTest
 
@@ -13,7 +13,7 @@ class TestDataProxy(BaseTest):
 
     def test_repr(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             field_a = fields.IntField(attribute='mongo_field_a')
             field_b = fields.StrField()
 
@@ -27,7 +27,7 @@ class TestDataProxy(BaseTest):
 
     def test_simple(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField()
 
@@ -45,7 +45,7 @@ class TestDataProxy(BaseTest):
 
     def test_load(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField(attribute='in_mongo_b')
 
@@ -66,7 +66,7 @@ class TestDataProxy(BaseTest):
 
     def test_modify(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField(attribute='in_mongo_b')
 
@@ -89,7 +89,7 @@ class TestDataProxy(BaseTest):
 
     def test_list_field_modify(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.ListField(fields.IntField())
             b = fields.ListField(fields.IntField(), attribute='in_mongo_b')
 
@@ -135,7 +135,7 @@ class TestDataProxy(BaseTest):
         class MyEmbedded(EmbeddedDocument):
             aa = fields.IntField()
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             # EmbeddedField need instance to retrieve implementation
             a = fields.EmbeddedField(MyEmbedded, instance=self.instance)
             b = fields.ListField(fields.IntField)
@@ -154,7 +154,7 @@ class TestDataProxy(BaseTest):
 
     def test_set(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField(attribute='in_mongo_b')
             c = fields.StrField(
@@ -188,7 +188,7 @@ class TestDataProxy(BaseTest):
 
     def test_del(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField(attribute='in_mongo_b')
 
@@ -206,7 +206,7 @@ class TestDataProxy(BaseTest):
 
     def test_route_naming(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             in_front = fields.IntField(attribute='in_mongo')
 
         MyDataProxy = data_proxy_factory('My', MySchema())
@@ -225,7 +225,7 @@ class TestDataProxy(BaseTest):
 
     def test_from_mongo(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             in_front = fields.IntField(attribute='in_mongo')
 
         MyDataProxy = data_proxy_factory('My', MySchema())
@@ -237,7 +237,7 @@ class TestDataProxy(BaseTest):
 
     def test_equality(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField(attribute='in_mongo_b')
 
@@ -257,7 +257,7 @@ class TestDataProxy(BaseTest):
 
     def test_share_ressources(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField(attribute='in_mongo_b')
 
@@ -272,7 +272,7 @@ class TestDataProxy(BaseTest):
 
     def test_set_to_missing_fields(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.IntField()
             b = fields.IntField(attribute='in_mongo_b')
 
@@ -291,7 +291,7 @@ class TestDataProxy(BaseTest):
         default_value = ObjectId('507f1f77bcf86cd799439011')
         default_callable = lambda: ObjectId('507f1f77bcf86cd799439012')
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             no_default = fields.ObjectIdField()
             with_default = fields.ObjectIdField(default=default_value)
             with_callable_default = fields.ObjectIdField(default=default_callable)
@@ -322,7 +322,7 @@ class TestDataProxy(BaseTest):
 
     def test_validate(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             with_max = fields.IntField(validate=validate.Range(max=99))
 
         MyDataProxy = data_proxy_factory('My', MySchema())
@@ -340,7 +340,7 @@ class TestDataProxy(BaseTest):
         class MyEmbedded(EmbeddedDocument):
             required = fields.IntField(required=True)
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             # EmbeddedField need instance to retrieve implementation
             required = fields.IntField(required=True)
             embedded = fields.EmbeddedField(MyEmbedded, instance=self.instance)
@@ -385,7 +385,7 @@ class TestDataProxy(BaseTest):
         }
 
     def test_unkown_field_in_db(self):
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             field = fields.IntField(attribute='mongo_field')
 
         DataProxy = data_proxy_factory('My', MySchema())
@@ -396,7 +396,7 @@ class TestDataProxy(BaseTest):
             d.from_mongo({'mongo_field': 42, 'xxx': 'foo'})
 
     def test_iterators(self):
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             field_a = fields.IntField(attribute='mongo_field_a')
             field_b = fields.IntField(attribute='mongo_field_b')
 
@@ -418,7 +418,7 @@ class TestNonStrictDataProxy(BaseTest):
 
     def test_build(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             pass
 
         strict_proxy = data_proxy_factory('My', MySchema(), strict=True)
@@ -427,7 +427,7 @@ class TestNonStrictDataProxy(BaseTest):
         assert issubclass(non_strict_proxy, BaseNonStrictDataProxy)
 
     def test_basic(self):
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             field_a = fields.IntField(attribute='mongo_field_a')
 
         NonStrictDataProxy = data_proxy_factory('My', MySchema(), strict=False)
diff --git a/tests/test_document.py b/tests/test_document.py
index 4ccd069..4df929f 100644
--- a/tests/test_document.py
+++ b/tests/test_document.py
@@ -5,7 +5,7 @@ import pytest
 
 from bson import ObjectId, DBRef
 
-from umongo import (Document, EmbeddedDocument, BaseSchema, fields, exceptions,
+from umongo import (Document, EmbeddedDocument, Schema, fields, exceptions,
                     post_dump, pre_load, validates_schema)
 
 from .common import BaseTest
@@ -451,7 +451,7 @@ class TestConfig(BaseTest):
             pass
 
         d = Doc()
-        assert isinstance(d.schema, BaseSchema)
+        assert isinstance(d.schema, Schema)
 
     def test_base_config(self):
 
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 70b0f45..8a4e7bb 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -8,7 +8,7 @@ from bson import ObjectId, DBRef, Decimal128
 from marshmallow import ValidationError, missing
 
 from umongo.data_proxy import data_proxy_factory
-from umongo import Document, EmbeddedDocument, BaseSchema, fields, Reference, validate
+from umongo import Document, EmbeddedDocument, Schema, fields, Reference, validate
 from umongo.data_objects import List, Dict
 
 from .common import BaseTest
@@ -92,7 +92,7 @@ class TestFields(BaseTest):
 
     def test_basefields(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             string = fields.StringField()
             uuid = fields.UUIDField()
             number = fields.NumberField()
@@ -159,7 +159,7 @@ class TestFields(BaseTest):
 
     def test_datetime(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.DateTimeField()
 
         s = MySchema()
@@ -191,7 +191,7 @@ class TestFields(BaseTest):
 
         timezone_2h = dt.timezone(dt.timedelta(hours=2), "test")
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.AwareDateTimeField()
             b = fields.AwareDateTimeField(default_timezone=timezone_2h)
 
@@ -221,7 +221,7 @@ class TestFields(BaseTest):
 
     def test_date(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             a = fields.DateField()
 
         s = MySchema()
@@ -242,7 +242,7 @@ class TestFields(BaseTest):
 
     def test_dict(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             dict = fields.DictField(attribute='in_mongo_dict', allow_none=True)
             kdict = fields.DictField(keys=fields.StringField(validate=validate.Length(0, 1)))
             vdict = fields.DictField(values=fields.IntField(validate=validate.Range(max=5)))
@@ -320,7 +320,7 @@ class TestFields(BaseTest):
 
     def test_dict_default(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             # Passing a mutable as default is a bad idea in real life
             d_dict = fields.DictField(values=fields.IntField, default={'1': 1, '2': 2})
             c_dict = fields.DictField(values=fields.IntField, default=lambda: {'1': 1, '2': 2})
@@ -408,7 +408,7 @@ class TestFields(BaseTest):
 
     def test_list(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             list = fields.ListField(fields.IntField(), attribute='in_mongo_list', allow_none=True)
 
         MyDataProxy = data_proxy_factory('My', MySchema())
@@ -493,7 +493,7 @@ class TestFields(BaseTest):
 
     def test_list_default(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             d_list = fields.ListField(fields.IntField(), default=(1, 2, 3))
             c_list = fields.ListField(fields.IntField(), default=lambda: (1, 2, 3))
 
@@ -582,7 +582,7 @@ class TestFields(BaseTest):
 
     def test_objectid(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             objid = fields.ObjectIdField(attribute='in_mongo_objid')
 
         MyDataProxy = data_proxy_factory('My', MySchema())
@@ -753,7 +753,7 @@ class TestFields(BaseTest):
 
     def test_decimal(self):
 
-        class MySchema(BaseSchema):
+        class MySchema(Schema):
             price = fields.DecimalField(attribute='in_mongo_price')
 
         MyDataProxy = data_proxy_factory('My', MySchema())
diff --git a/tests/test_marshmallow.py b/tests/test_marshmallow.py
index 537b202..d74bbd6 100644
--- a/tests/test_marshmallow.py
+++ b/tests/test_marshmallow.py
@@ -6,9 +6,9 @@ from bson import ObjectId
 import marshmallow
 
 from umongo import Document, EmbeddedDocument, fields, set_gettext, validate, missing
-from umongo import marshmallow_bonus as ma_bonus_fields
-from umongo.abstract import BaseField, BaseSchema
-from umongo.marshmallow_bonus import schema_from_umongo_get_attribute, SchemaFromUmongo
+from umongo import marshmallow_bonus as ma_bonus_fields, Schema
+from umongo.abstract import BaseField
+from umongo.schema import schema_from_umongo_get_attribute, SchemaFromUmongo
 
 from .common import BaseTest
 
@@ -39,7 +39,7 @@ class TestMarshmallow(BaseTest):
     def test_by_schema(self):
         ma_schema_cls = self.User.schema.as_marshmallow_schema()
         assert issubclass(ma_schema_cls, marshmallow.Schema)
-        assert not issubclass(ma_schema_cls, BaseSchema)
+        assert not issubclass(ma_schema_cls, Schema)
 
     def test_custom_ma_base_schema_cls(self):