class AutoSchema(with_metaclass(ModelSchemaMeta, Schema)): """ A Marshmallow schema that does field introspection based on the SQLAlchemy model specified in Meta.model. Unlike the marshmallow_sqlalchemy ModelSchema, it doesn't change the serialization and deserialization proccess. """ OPTIONS_CLASS = CustomModelSchemaOpts # Use NullToBlankString instead of fields.String by default on text fields TYPE_MAPPING = Schema.TYPE_MAPPING.copy() TYPE_MAPPING[str] = NullToBlankString
class NamedTupleSchema(with_metaclass(NamedTupleSchemaMeta, ma.Schema)): OPTIONS_CLASS = NamedTupleSchemaOpts @ma.post_load def make_namedtuple(self, data): return self.opts.namedtuple(**data) @ma.post_dump def clear_optional(self, data): return { k: v for k, v in data.items() if v is not None or self.opts.namedtuple._field_defaults.get(k) is not None }
class TableSchema(with_metaclass(TableSchemaMeta, ma.Schema)): """Base class for SQLAlchemy model-based Schemas. Example: :: from rest_utils import TableSchema from mymodels import engine, users class UserSchema(TableSchema): class Meta: table = users schema = UserSchema() select = users.select().limit(1) user = engine.execute(select).fetchone() serialized = schema.dump(user).data """ OPTIONS_CLASS = TableSchemaOpts
class ModelSchema(with_metaclass(SchemaMeta, ma.Schema)): """Base class for SQLAlchemy model-based Schemas. Example: :: from marshmallow_sqlalchemy import ModelSchema from mymodels import User, session class UserSchema(ModelSchema): class Meta: model = User sqla_session = session """ OPTIONS_CLASS = SchemaOpts def make_object(self, data): return self.opts.model(**data)
class ModelResource(with_metaclass(ModelResourceMeta, Resource)): manager = None @Route.GET('', rel="instances") def instances(self, **kwargs): return self.manager.instances(**kwargs) instances.request_schema = instances.response_schema = 'collection' @instances.POST(rel="create") def create(self, properties): item = self.manager.create(properties) return item @Route.GET('/<int:id>', rel="self", attribute="instance") def read(self, id, **kwargs): print("read({}, {})".format(kwargs, id)) return self.manager.read(id) @read.PATCH(rel="update") def update(self, properties, id): item = self.manager.read(id) updated_item = self.manager.update(item, properties) return updated_item @update.DELETE(rel="destroy") def destroy(self, id): return "destroy {}".format(id) class Meta: model = None id_attribute = None # use 'id' by default sort_attribute = None # None means use id_attribute for sort id_converter = None include_id = True include_type = False filters = True class Schema: pass
class ModelSchema(with_metaclass(SchemaMeta, ma.Schema)): OPTIONS_CLASS = SchemaOpts def __init__(self, instance=None, **kwargs): self.instance = instance super(ModelSchema, self).__init__(**kwargs) @ma.post_load def make_instance(self, data): """Build object from data.""" if not self.opts.model: return data if self.instance is not None: for key, value in data.items(): setattr(self.instance, key, value) return self.instance return self.opts.model(**data) def load(self, data, instance=None, *args, **kwargs): self.instance = instance or self.instance return super(ModelSchema, self).load(data, *args, **kwargs)
class Resource(with_metaclass(ResourceMeta, object)): api = None schema = None route_prefix = None class Meta: name = None title = None description = None exclude_routes = () route_decorators = {} @Route.GET('/schema', rel="describedBy", attribute="schema") def described_by(self): schema = OrderedDict([ ("$schema", "http://json-schema.org/draft-04/hyper-schema#"), ]) for prop in ('title', 'description'): value = getattr(self.meta, prop) if value: schema[prop] = value links = [route for name, route in sorted(self.routes.items())] if self.schema: schema["type"] = "object" schema.update(self.schema.response) schema["links"] = [ link.schema_factory(self) for link in sorted(links, key=attrgetter('relation')) ] return schema, 200
class Schema(with_metaclass(SchemaMeta, BaseSchema)): __doc__ = BaseSchema.__doc__
class ModelSchema(with_metaclass(ModelSchemaMeta, ma.Schema)): """Base class for SQLAlchemy model-based Schemas. Example: :: from marshmallow_sqlalchemy import ModelSchema from mymodels import User, session class UserSchema(ModelSchema): class Meta: model = User schema = UserSchema() user = schema.load({'name': 'Bill'}, session=session) existing_user = schema.load({'name': 'Bill'}, instance=User.query.first()) :param session: Optional SQLAlchemy session; may be overridden in `load.` :param instance: Optional existing instance to modify; may be overridden in `load`. """ OPTIONS_CLASS = ModelSchemaOpts @property def session(self): return self._session or self.opts.sqla_session @session.setter def session(self, session): self._session = session @property def transient(self): return self._transient or self.opts.transient @transient.setter def transient(self, transient): self._transient = transient def __init__(self, *args, **kwargs): self._session = kwargs.pop("session", None) self.instance = kwargs.pop("instance", None) self._transient = kwargs.pop("transient", None) super(ModelSchema, self).__init__(*args, **kwargs) def get_instance(self, data): """Retrieve an existing record by primary key(s). If the schema instance is transient, return None. :param data: Serialized data to inform lookup. """ if self.transient: return None props = get_primary_keys(self.opts.model) filters = {prop.key: data.get(prop.key) for prop in props} if None not in filters.values(): return self.session.query( self.opts.model).filter_by(**filters).first() return None @ma.post_load def make_instance(self, data): """Deserialize data to an instance of the model. Update an existing row if specified in `self.instance` or loaded by primary key(s) in the data; else create a new row. :param data: Data to deserialize. """ instance = self.instance or self.get_instance(data) if instance is not None: for key, value in iteritems(data): setattr(instance, key, value) return instance kwargs, association_attrs = self._split_model_kwargs_association(data) instance = self.opts.model(**kwargs) for attr, value in iteritems(association_attrs): setattr(instance, attr, value) return instance def load(self, data, session=None, instance=None, transient=False, *args, **kwargs): """Deserialize data to internal representation. :param session: Optional SQLAlchemy session. :param instance: Optional existing instance to modify. :param transient: Optional switch to allow transient instantiation. """ self._session = session or self._session self._transient = transient or self._transient if not (self.transient or self.session): raise ValueError("Deserialization requires a session") self.instance = instance or self.instance try: return super(ModelSchema, self).load(data, *args, **kwargs) finally: self.instance = None def validate(self, data, session=None, *args, **kwargs): self._session = session or self._session if not (self.transient or self.session): raise ValueError("Validation requires a session") return super(ModelSchema, self).validate(data, *args, **kwargs) def _split_model_kwargs_association(self, data): """Split serialized attrs to ensure association proxies are passed separately. This is necessary for Python < 3.6.0, as the order in which kwargs are passed is non-deterministic, and associations must be parsed by sqlalchemy after their intermediate relationship, unless their `creator` has been set. Ignore invalid keys at this point - behaviour for unknowns should be handled elsewhere. :param data: serialized dictionary of attrs to split on association_proxy. """ association_attrs = { key: value for key, value in iteritems(data) # association proxy if hasattr(getattr(self.opts.model, key, None), "remote_attr") } kwargs = { key: value for key, value in iteritems(data) if (hasattr(self.opts.model, key) and key not in association_attrs) } return kwargs, association_attrs
class ModelSchema(with_metaclass(ModelSchemaMeta, ma.Schema)): """Base class for SQLAlchemy model-based Schemas. Example: :: from rest_utils import ModelSchema from mymodels import User, session class UserSchema(ModelSchema): class Meta: model = User or class UserSchema(ModelSchema): __model__ = User schema = UserSchema() user = schema.load({'name': 'Bill'}, session=session) existing_user = schema.load({'name': 'Bill'}, instance=User.query.first()) :param session: Optional SQLAlchemy session; may be overridden in `load.` :param instance: Optional existing instance to modify; may be overridden in `load`. """ OPTIONS_CLASS = ModelSchemaOpts def __init__(self, related_kwargs={}, check_existence=False, *args, **kwargs): """ :param only: :param exclude: :param related_kwargs: fields.Related字段展开时的参数。如{UserSchema: {"exclude": ["id"]}} :param check_existence: 创建资源之前先检查是否存在 :param args: :param kwargs: """ if not self.opts.model: raise ValueError('ModelSchema requires a sa orm model.') self.model = self.opts.model self._instance = kwargs.pop('instance', None) # instance必须存在于数据库中 self._session = kwargs.pop('session', None) # self.ext_data = dict() # 展开层级 self._current_expand = None # 创建资源之前先检查是否存在 self._check_existence = check_existence self._related_kwargs = related_kwargs super(ModelSchema, self).__init__(*args, **kwargs) @ma.post_load def make_instance(self, data): """Deserialize data to an instance of the model. Update an existing row loaded by primary key(s) in the data; else create a new row. :param data: Data to deserialize. """ if self._check_existence: instance = self._instance or get_instance(self._session, self.model, data) if instance is not None: # 检查是否需要变更 if check_need_modify(instance, data): instance = self.opts.update(instance, data) add_padding_callback(self.opts.updated, instance) # commit数据库之后调用 return instance instance = self.opts.create(self.model, data) add_padding_callback(self.opts.created, instance) # commit数据库之后调用 return instance def dump(self, obj, many=None, expand=0, **kwargs): """ serialize :param obj: :param many: :param expand: 当expand>=1或者None时,field.Related生效,展开展开子资源 :param kwargs: :return: """ old_exclude = self.exclude old_only = self.only try: self._current_expand = expand if self._current_expand is not None and self._current_expand <= 0: exclude = list(old_exclude) if old_exclude else [] only = list(old_only) if old_only else [] for key, field in iteritems(self._declared_fields): if isinstance(field, Related): if key not in exclude: exclude.append(key) if key in only: only.remove(key) if exclude: self.exclude = exclude if only: self.only = only return super(ModelSchema, self).dump(obj, many=many, **kwargs) finally: self._current_expand = None self.exclude = old_exclude self.only = old_only def load(self, data, *args, **kwargs): """Deserialize data to internal representation. :param session: Optional SQLAlchemy session. :param instance: Optional existing instance to modify. """ if not self._session: raise ValueError('Deserialization requires a session') return super(ModelSchema, self).load(data, *args, **kwargs) def validate(self, data, *args, **kwargs): if not self._session: raise ValueError('Validation requires a session') return super(ModelSchema, self).validate(data, *args, **kwargs)
class ModelSchema(with_metaclass(SchemaMeta, ma.Schema)): """Base class for Mongoengine model-based Schemas. Example: :: from marshmallow_mongoengine import ModelSchema from mymodels import User class UserSchema(ModelSchema): class Meta: model = User """ OPTIONS_CLASS = SchemaOpts @ma.post_dump def _remove_skip_values(self, data): to_skip = self.opts.model_skip_values return { key: value for key, value in data.items() if value not in to_skip } @ma.post_load def _make_object(self, data): if self.opts.model_build_obj and self.opts.model: return self.opts.model(**data) else: return data def update(self, obj, data): """Helper function to update an already existing document instead of creating a new one. :param obj: Mongoengine Document to update :param data: incomming payload to deserialize :return: an :class UnmarshallResult: Example: :: from marshmallow_mongoengine import ModelSchema from mymodels import User class UserSchema(ModelSchema): class Meta: model = User def update_obj(id, payload): user = User.objects(id=id).first() result = UserSchema().update(user, payload) result.data is user # True Note: Given the update is done on a existing object, the required param on the fields is ignored """ # TODO: find a cleaner way to skip required validation on update required_fields = [k for k, f in self.fields.items() if f.required] for field in required_fields: self.fields[field].required = False errors = {} try: loaded_data = self._do_load(data, postprocess=False) except ValidationError as err: errors = err.messages valid_data = err.valid_data for field in required_fields: self.fields[field].required = True if not errors: # Update the given obj fields for k, v in loaded_data.items(): # Skip default values that have been automatically # added during unserialization if k in data: setattr(obj, k, v) return obj
class ModelSchema(with_metaclass(ModelSchemaMeta, ma.Schema)): """Base class for SQLAlchemy model-based Schemas. Example: :: from marshmallow_sqlalchemy import ModelSchema from mymodels import User, session class UserSchema(ModelSchema): class Meta: model = User schema = UserSchema() user = schema.load({'name': 'Bill'}, session=session) existing_user = schema.load({'name': 'Bill'}, instance=User.query.first()) :param session: Optional SQLAlchemy session; may be overridden in `load.` :param instance: Optional existing instance to modify; may be overridden in `load`. """ OPTIONS_CLASS = ModelSchemaOpts def __init__(self, *args, **kwargs): session = kwargs.pop('session', None) self.instance = kwargs.pop('instance', None) super(ModelSchema, self).__init__(*args, **kwargs) self.session = session or self.opts.sqla_session def get_instance(self, data): """Retrieve an existing record by primary key(s).""" props = get_primary_keys(self.opts.model) filters = { prop.key: data.get(prop.key) for prop in props } if None not in filters.values(): return self.session.query( self.opts.model ).filter_by( **filters ).first() return None @ma.post_load def make_instance(self, data): """Deserialize data to an instance of the model. Update an existing row if specified in `self.instance` or loaded by primary key(s) in the data; else create a new row. :param data: Data to deserialize. """ instance = self.instance or self.get_instance(data) if instance is not None: for key, value in iteritems(data): setattr(instance, key, value) return instance return self.opts.model(**data) def load(self, data, session=None, instance=None, *args, **kwargs): """Deserialize data to internal representation. :param session: Optional SQLAlchemy session. :param instance: Optional existing instance to modify. """ self.session = session or self.session self.instance = instance or self.instance if not self.session: raise ValueError('Deserialization requires a session') ret = super(ModelSchema, self).load(data, *args, **kwargs) self.instance = None return ret def validate(self, data, session=None, *args, **kwargs): self.session = session or self.session if not self.session: raise ValueError('Validation requires a session') return super(ModelSchema, self).validate(data, *args, **kwargs)
class Serializer(with_metaclass(SerializerMeta, BaseSerializer)): __doc__ = BaseSerializer.__doc__
class ApiSchema(with_metaclass(ApiSchemaMeta, Schema)): OPTIONS_CLASS = ApiSchemaOptions
class ExtSchema(with_metaclass(ExtSchemaMeta, BaseSchema)): __doc__ = BaseSchema.__doc__ @property def api_url(self): return getattr(settings, 'API_URL', 'https://api.dane.gov.pl') @property def _many(self): return getattr(self, 'many', False) @property def _fields(self): return getattr(self, 'fields', getattr(self, '_declared_fields', None)) @property # noqa:C901 def doc_schema(self): _meta_cls = getattr(self, 'Meta', None) _declared_fields = getattr(self, '_declared_fields', dict()) if getattr(_meta_cls, 'fields', None) or getattr( _meta_cls, 'additional', None): declared_fields = set(_declared_fields.keys()) if (set(getattr(_meta_cls, 'fields', set())) > declared_fields or set(getattr(_meta_cls, 'additional', set())) > declared_fields): warnings.warn( 'Only explicitly-declared fields will be included in the Schema Object. ' 'Fields defined in Meta.fields or Meta.additional are ignored.', ) jsonschema = { 'type': 'object', 'properties': OrderedDict(), } if hasattr(_meta_cls, 'nullable'): jsonschema['nullable'] = True exclude = set(getattr(_meta_cls, 'exclude', [])) for field_name, field_obj in self._fields.items(): if field_name in exclude or field_obj.dump_only or field_obj.load_only: continue _field_name = field_obj.data_key or field_name jsonschema['properties'][_field_name] = field_obj.openapi_property partial = getattr(self, 'partial', None) if field_obj.required: if not partial or (is_collection(partial) and field_name not in partial): jsonschema.setdefault('required', []).append(_field_name) if 'required' in jsonschema: jsonschema['required'].sort() if _meta_cls is not None: if hasattr(_meta_cls, 'title'): jsonschema['title'] = _meta_cls.title if hasattr(_meta_cls, 'description'): jsonschema['description'] = _meta_cls.description if self._many: jsonschema = { 'type': 'array', 'items': jsonschema, } return jsonschema def get_queryset(self, queryset, data): return queryset @classmethod def prepare(cls): pass
class CSVSerializer(with_metaclass(CSVSchemaMeta, BaseSchema)): __doc__ = BaseSchema.__doc__ OPTIONS_CLASS = CSVSerializerOpts