2017-02-14 3 views
1

Скажем, у меня есть схемы, как это:Определение полей программно в Зефир схемы

class MySchema(Schema): 

    field_1 = Float() 
    field_2 = Float() 
    ... 
    field_42 = Float() 

Есть ли способ, чтобы добавить эти поля программно в классе?

Что-то вроде этого:

class MyClass(BaseClass): 

    FIELDS = ('field_1', 'field_2',..., 'field_42') 

    for field in FIELDS: 
     setattr(?, field, Float()) # What do I replace this "?" with? 

Я видел сообщения о добавлении атрибутов динамически экземпляров класса, но это отличается тем, что

  • Я не хочу, чтобы залатать экземпляр, но класс
  • Зефир Схема использует метакласс

эль AME вопрос может применяться к другим библиотекам определения модели, как ODM/ORM (uMongo/MongoEngine, SQL алхимия, ...)

ответ

4

Все, что вам нужно сделать, это использовать тип() функцию, чтобы построить свой класс с любыми атрибутами вы хотите:

MySchema = type('MySchema', (marshmallow.Schema,), { 
    attr: marshmallow.fields.Float() 
    for attr in FIELDS 
}) 

Вы даже можете иметь различные типы полей там:

fields = {} 
fields['foo'] = marshmallow.fields.Float() 
fields['bar'] = marshmallow.fields.String() 
MySchema = type('MySchema', (marshmallow.Schema,), fields) 

или в качестве основы для ваших настроек:

class MySchema(type('_MySchema', (marshmallow.Schema,), fields)): 
    @marshmallow.post_dump 
    def update_something(self, data): 
     pass 
+0

Это здорово. Он использует только Python, а не специфические механизмы Marshmallow. –

0

мне удалось сделать это с помощью подклассов метакласса по умолчанию:

class MySchemaMeta(SchemaMeta): 

    @classmethod 
    def get_declared_fields(mcs, klass, cls_fields, inherited_fields, dict_cls): 
     fields = super().get_declared_fields(klass, cls_fields, inherited_fields, dict_cls) 
     FIELDS = ('field_1', 'field_2',..., 'field_42') 
     for field in FIELDS: 
      fields.update({fluid: Float()}) 
     return fields 

class MySchema(Schema, metaclass=MySchemaMeta): 

    class Meta: 
     strict = True 

я сделал это более общий характер:

class DynamicSchemaOpts(SchemaOpts): 

    def __init__(self, meta): 
     super().__init__(meta) 
     self.auto_fields = getattr(meta, 'auto_fields', []) 


class DynamicSchemaMeta(SchemaMeta): 

    @classmethod 
    def get_declared_fields(mcs, klass, cls_fields, inherited_fields, dict_cls): 

     fields = super().get_declared_fields(klass, cls_fields, inherited_fields, dict_cls) 

     for auto_field_list in klass.opts.auto_fields: 
      field_names, field = auto_field_list 
      field_cls = field['cls'] 
      field_args = field.get('args', []) 
      field_kwargs = field.get('kwargs', {}) 
      for field_name in field_names: 
       fields.update({field_name: field_cls(*field_args, **field_kwargs)}) 

     return fields 


class MySchema(Schema, metaclass=DynamicSchemaMeta): 

    OPTIONS_CLASS = DynamicSchemaOpts 

    class Meta: 
     strict = True 
     auto_fields = [ 
      (FIELDS, 
      {'cls': Float}), 
     ] 

Я не писал

class Meta: 
    strict = True 
    auto_fields = [ 
     (FIELDS, Float()), 
    ] 

, потому что тогда все эти поля будут Sh те же Field экземпляр.

Field и его арг/kwargs должны быть указаны отдельно:

class Meta: 
     strict = True 
     auto_fields = [ 
      (FIELDS, 
      {'cls': Nested, 
       'args': (MyEmbeddedSchema), 
       'kwargs': {'required': True} 
      }), 
     ] 

У меня нет ни одного примера, случай использования неисправного из-за нескольких полей, разделяющих один и тот же экземпляр, но это не звучит безопасно. Если эта мера предосторожности бесполезно, то код может быть упрощен и более удобным для чтения:

class Meta: 
     strict = True 
     auto_fields = [ 
      (FIELDS, Nested(MyEmbeddedSchema, required=True)), 
     ] 

Очевидно, что этот ответ является специфическим для Зефир и не относится к другим библиотекам ODM/ОРМ.

Смежные вопросы