Пример #1
0
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
Пример #2
0
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
        }
Пример #3
0
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
Пример #4
0
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)
Пример #5
0
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
Пример #6
0
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)
Пример #7
0
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
Пример #8
0
class Schema(with_metaclass(SchemaMeta, BaseSchema)):
    __doc__ = BaseSchema.__doc__
Пример #9
0
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
Пример #10
0
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)
Пример #11
0
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
Пример #12
0
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)
Пример #13
0
class Serializer(with_metaclass(SerializerMeta, BaseSerializer)):
    __doc__ = BaseSerializer.__doc__
Пример #14
0
class ApiSchema(with_metaclass(ApiSchemaMeta, Schema)):
    OPTIONS_CLASS = ApiSchemaOptions
Пример #15
0
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
Пример #16
0
class CSVSerializer(with_metaclass(CSVSchemaMeta, BaseSchema)):
    __doc__ = BaseSchema.__doc__
    OPTIONS_CLASS = CSVSerializerOpts