Example #1
0
   async def test_post_invalid_json(self, init_db, headers, root_path, client):
        client = await client
        user = [{
            'name': 'test2',
            'email': 'test2',
            'password': '******',
            'grants': [{
                'test': 1,
                'method_id': 1
            }]
        }]
        resp = await client.post('/users', data=ujson.dumps(user), headers=headers)

        assert resp.status == 400
        result = (await resp.json())
        message = result.pop('message')
        expected_schema = ujson.dumps(get_swagger_json(root_path + '/../myreco/users/models.py'),
                                      escape_forward_slashes=False)
        expected_schema = \
            expected_schema.replace('#/definitions/grants', '#/definitions/UsersModel.grants')\
            .replace('#/definitions/method', '#/definitions/UsersModel.method')\
            .replace('#/definitions/uri', '#/definitions/UsersModel.uri')
        expected_schema = ujson.loads(expected_schema)

        fail_msg = "Failed validating instance['0']['grants']['0'] "\
                   "for schema['items']['allOf']['1']['properties']['grants']['items']['oneOf']"
        assert message == \
                "{'method_id': 1, 'test': 1} is not valid under any of the given schemas. " + fail_msg \
            or message == \
                "{'test': 1, 'method_id': 1} is not valid under any of the given schemas. " + fail_msg
        assert result == {
            'instance': {'method_id': 1, 'test': 1},
            'schema': expected_schema['definitions']['grants']
        }
Example #2
0
class StoresModelBase(AbstractConcreteBase):
    __tablename__ = 'stores'
    __swagger_json__ = get_swagger_json(__file__)
    __table_args__ = (sa.UniqueConstraint('name', 'country'), )

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(255), nullable=False)
    country = sa.Column(sa.String(255), nullable=False)
    configuration_json = sa.Column(sa.Text, nullable=False)

    @property
    def configuration(self):
        if not hasattr(self, '_configuration'):
            self._configuration = ujson.loads(self.configuration_json)
        return self._configuration

    async def _setattr(self, attr_name, value, session, input_):
        if attr_name == 'configuration':
            value = ujson.dumps(value)
            attr_name = 'configuration_json'

        await super()._setattr(attr_name, value, session, input_)

    def _format_output_json(self, dict_inst, schema):
        if schema.get('configuration') is not False:
            config = dict_inst.pop('configuration_json')
            dict_inst['configuration'] = self.configuration
Example #3
0
def extend_swagger_json(original,
                        current_filename,
                        swagger_json_name=None,
                        by_method=False):
    swagger_json = deepcopy(original)
    additional_swagger = get_swagger_json(current_filename, swagger_json_name)

    if by_method:
        for path_name, addit_path in additional_swagger['paths'].items():
            if path_name in swagger_json['paths']:
                path = swagger_json['paths'][path_name]

                for method_name, method in addit_path.items():
                    if method_name in path:
                        path[method_name].update(method)
                    else:
                        path[method_name] = method

    else:
        swagger_json['paths'].update(additional_swagger['paths'])

    definitions = swagger_json.get('definitions')
    additional_definitions = additional_swagger.get('definitions')

    if additional_definitions:
        if definitions:
            definitions.update(additional_definitions)
        else:
            swagger_json['definitions'] = additional_definitions

    return swagger_json
Example #4
0
    def _validate_input(self, schema):
        validate(schema,
                 get_swagger_json(__file__, 'store_items_metaschema.json'))

        for id_name in schema['id_names']:
            if id_name not in schema.get('properties', {}):
                raise ValidationError(
                    "id_name '{}' was not found in schema properties".format(
                        id_name),
                    instance=schema['id_names'],
                    schema=schema)
Example #5
0
class EngineStrategiesModelBase(AbstractConcreteBase):
    __tablename__ = 'engine_strategies'
    __swagger_json__ = get_swagger_json(__file__)
    _jobs = dict()
    __table_args__ = (sa.UniqueConstraint('class_name', 'class_module'), )

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(255), unique=True, nullable=False)
    class_name = sa.Column(sa.String(255), nullable=False)
    class_module = sa.Column(sa.String(255), nullable=False)

    async def _validate(self, session, input_):
        self.get_class()

    @classmethod
    def _get_class(cls, strategy):
        return ModuleObjectLoader.load({
            'path': strategy['class_module'],
            'object_name': strategy['class_name']
        })

    def get_class(self):
        if not hasattr(self, '_class'):
            self._class = type(self)._get_class(
                self.todict({'object_types': False}))

        return self._class

    @property
    def object_types(self):
        return self.get_class().object_types.keys()

    def _format_output_json(self, dict_inst, schema):
        if schema.get('object_types') is not False:
            dict_inst['object_types'] = self.object_types

    @classmethod
    def get_instance(cls, engine, items_model=None):
        return cls._get_class(engine['strategy'])(engine, items_model)
Example #6
0
class GrantsModelBase(AbstractConcreteBase):
    __tablename__ = 'grants'
    __table_args__ = (sa.UniqueConstraint('uri_id', 'method_id'), )
    __swagger_json__ = get_swagger_json(__file__, 'grants_swagger.json')

    id = sa.Column(sa.Integer, primary_key=True)

    @declared_attr
    def uri_id(cls):
        return sa.Column(sa.ForeignKey('uris.id'))

    @declared_attr
    def method_id(cls):
        return sa.Column(sa.ForeignKey('methods.id'))

    @declared_attr
    def uri(cls):
        return sa.orm.relationship('URIsModel')

    @declared_attr
    def method(cls):
        return sa.orm.relationship('MethodsModel')
Example #7
0
class _ItemTypesModelBase(AbstractConcreteBase):
    __tablename__ = 'item_types'
    __swagger_json__ = get_swagger_json(__file__)
    __schema_dir__ = get_dir_path(__file__)

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(255), unique=True, nullable=False)
    schema_json = sa.Column(sa.Text, nullable=False)
    store_items_class_json = sa.Column(sa.Text)

    @declared_attr
    def stores(cls):
        return sa.orm.relationship('StoresModel',
                                   uselist=True,
                                   secondary='item_types_stores')

    @property
    def store_items_class(self):
        if not hasattr(self, '_store_items_class'):
            self._store_items_class = \
                ujson.loads(self.store_items_class_json) if self.store_items_class_json \
                    is not None else None
        return self._store_items_class

    async def _setattr(self, attr_name, value, session, input_):
        if attr_name == 'schema':
            self._validate_input(value)
            value = ujson.dumps(value)
            attr_name = 'schema_json'

        if attr_name == 'store_items_class':
            value = ujson.dumps(value)
            attr_name = 'store_items_class_json'

        await super()._setattr(attr_name, value, session, input_)

    def _validate_input(self, schema):
        validate(schema,
                 get_swagger_json(__file__, 'store_items_metaschema.json'))

        for id_name in schema['id_names']:
            if id_name not in schema.get('properties', {}):
                raise ValidationError(
                    "id_name '{}' was not found in schema properties".format(
                        id_name),
                    instance=schema['id_names'],
                    schema=schema)

    def _format_output_json(self, dict_inst, todict_schema):
        if todict_schema.get('schema') is not False:
            if 'schema_json' in dict_inst:
                dict_inst['schema'] = ujson.loads(dict_inst.pop('schema_json'))

                schema_properties = dict_inst['schema'].get('properties', {})
                schema_properties_names = sorted(schema_properties.keys())
                dict_inst['available_filters'] = \
                    [{'name': name, 'schema': schema_properties[name]} \
                        for name in schema_properties_names if name != '_operation']

        if todict_schema.get('store_items_class') is not False:
            dict_inst.pop('store_items_class_json')
            dict_inst['store_items_class'] = self.store_items_class

    @classmethod
    async def swagger_get_filter_types(cls, req, session):
        filters_factory = cls.get_model('slot_filters').__factory__
        body = ujson.dumps(filters_factory.get_filter_types())
        return cls._build_response(200, body=body)
Example #8
0
import sqlalchemy as sa
from jsonschema import ValidationError, validate
from jsonschema.validators import Draft4Validator, create
from myreco.engine_strategies.filters.filters import BooleanFilterBy
from myreco.item_types._store_items_model_meta import _StoreItemsModelBaseMeta
from myreco.utils import ModuleObjectLoader, build_class_name, build_item_key
from sqlalchemy.ext.declarative import AbstractConcreteBase, declared_attr
from swaggerit.method import SwaggerMethod
from swaggerit.models.orm.factory import FactoryOrmModels
from swaggerit.request import SwaggerRequest
from swaggerit.utils import get_dir_path, get_swagger_json

import ujson

store_items_metaschema = get_swagger_json(__file__,
                                          'store_items_metaschema.json')
ItemValidator = create(store_items_metaschema, Draft4Validator.VALIDATORS)
ItemValidator.DEFAULT_TYPES['simpleObject'] = dict


class _ItemTypesModelBase(AbstractConcreteBase):
    __tablename__ = 'item_types'
    __swagger_json__ = get_swagger_json(__file__)
    __schema_dir__ = get_dir_path(__file__)

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(255), unique=True, nullable=False)
    schema_json = sa.Column(sa.Text, nullable=False)
    store_items_class_json = sa.Column(sa.Text)

    @declared_attr
Example #9
0
class PlacementsModelBase(AbstractConcreteBase):
    __tablename__ = 'placements'
    __swagger_json__ = get_swagger_json(__file__)

    hash = sa.Column(sa.String(255), unique=True, nullable=False)
    small_hash = sa.Column(sa.String(255), primary_key=True)
    name = sa.Column(sa.String(255), nullable=False)
    ab_testing = sa.Column(sa.Boolean, default=False)
    show_details = sa.Column(sa.Boolean, default=True)
    distribute_items = sa.Column(sa.Boolean, default=False)
    is_redirect = sa.Column(sa.Boolean, default=False)

    @declared_attr
    def store_id(cls):
        return sa.Column(sa.ForeignKey('stores.id'), nullable=False)

    @declared_attr
    def variations(cls):
        return sa.orm.relationship('VariationsModel',
                                   uselist=True,
                                   passive_deletes=True)

    async def init(self, session, input_=None, **kwargs):
        await super().init(session, input_=input_, **kwargs)
        self._set_hash()

    def _set_hash(self):
        if self.name and not self.hash:
            hash_ = hashlib.new('ripemd160')
            hash_.update(self.name.encode() + bytes(self.store_id))
            self.hash = hash_.hexdigest()

    def __setattr__(self, name, value):
        if name == 'hash':
            self.small_hash = value[:5]

        super().__setattr__(name, value)

    @classmethod
    async def get_items(cls, req, session):
        placement = await cls._get_placement(req, session)
        if placement is None:
            return cls._build_response(404)

        explict_fallbacks = req.query.pop('explict_fallbacks', False)
        input_external_variables = req.query
        show_details = req.query.pop('show_details',
                                     placement.get('show_details'))
        distribute_items = placement.get('distribute_items')
        recos = slots = []
        recos_key = 'slots'
        slots_coros = []

        for slot in placement['variations'][0]['slots']:
            coro = cls._get_slot_recos_async(slot, input_external_variables,
                                             session, show_details)
            slots_coros.append(Task(coro))

        for coro in slots_coros:
            slots.append(await coro)

        valid_slots = []
        for slot in slots:
            slot_recos = slot['items']
            has_fallback = [
                True for fallback in slot_recos['fallbacks'] if fallback
            ]
            if slot_recos['main'] or has_fallback:
                valid_slots.append(slot)

        if not valid_slots:
            return cls._build_response(404)

        if not explict_fallbacks or placement['is_redirect']:
            for slot in slots:
                slot['items'] = cls._get_all_slot_recos(slot['items'])

            if distribute_items:
                recos_key = 'distributed_items'
                recos = cls._get_all_recos_from_slots(slots)
                recos = cls._distribute_items(recos)

        if placement['is_redirect']:
            return cls._build_redirect_response(recos, distribute_items, req)

        else:
            placement = {
                'name': placement['name'],
                'small_hash': placement['small_hash']
            }
            placement[recos_key] = recos

            return cls._build_recos_response(placement)

    @classmethod
    async def _get_placement(cls, req, session):
        small_hash = req.path_params['small_hash']
        placements = await cls.get(session, {'small_hash': small_hash})

        if not placements:
            return None

        return placements[0]

    @classmethod
    async def _get_slot_recos_async(cls, slot, input_external_variables,
                                    session, show_details):
        slot_recos = {'fallbacks': []}
        slot_recos['main'] = \
            await cls._get_recos_by_slot(slot, input_external_variables, session, show_details)

        await cls._get_fallbacks_recos(slot_recos, slot,
                                       input_external_variables, session,
                                       show_details)

        slot = {
            'name': slot['name'],
            'item_type': slot['engine']['item_type']['name']
        }
        slot['items'] = slot_recos

        return slot

    @classmethod
    async def _get_recos_by_slot(cls,
                                 slot,
                                 input_external_variables,
                                 session,
                                 show_details,
                                 max_items=None):
        try:
            engine = slot['engine']
            items_model = get_items_model(engine['item_type'],
                                          engine['store_id'])
            engine_vars = cls._get_slot_variables(slot,
                                                  input_external_variables)
            filters = cls._get_slot_filters(slot, input_external_variables,
                                            items_model)
            engine_strategy_model = cls.get_model('engine_strategies')
            strategy_instance = engine_strategy_model.get_instance(
                engine, items_model)
            max_items = slot['max_items'] if max_items is None else max_items

            return await strategy_instance.get_items(session, filters,
                                                     max_items, show_details,
                                                     items_model,
                                                     **engine_vars)

        except Exception as error:
            cls._logger.debug('Input Variables:\n' +
                              ujson.dumps(input_external_variables, indent=4))
            cls._logger.exception('Slot:\n' + ujson.dumps(slot, indent=4))
            return []

    @classmethod
    def _get_slot_variables(cls, slot, input_external_variables):
        engine_vars = dict()

        for slot_var in slot['slot_variables']:
            var_name = slot_var['external_variable']['name']
            var_engine_name = slot_var['engine_variable_name']

            if var_name in input_external_variables:
                var_value = input_external_variables[var_name]
                schema = cls._get_external_variable_schema(
                    slot, var_engine_name)

                if schema is not None:
                    engine_vars[var_engine_name] = JsonBuilder.build(
                        var_value, schema)

        return engine_vars

    @classmethod
    def _get_external_variable_schema(cls, slot, var_name):
        for var in slot['engine']['variables']:
            if var['name'] == var_name:
                return var['schema']

    @classmethod
    def _get_slot_filters(cls, slot, input_external_variables, items_model):
        filters = dict()

        for slot_filter in slot['slot_filters']:
            var_name = slot_filter['external_variable']['name']
            prop_name = slot_filter['property_name']
            is_overridable = slot_filter['override'] and slot_filter[
                'override_value']

            if var_name in input_external_variables:
                if is_overridable:
                    var_value = slot_filter['override_value']
                else:
                    var_value = input_external_variables[var_name]

                cls._set_filter(filters, slot_filter, slot, var_value,
                                items_model)

            elif is_overridable:
                var_value = slot_filter['override_value']
                cls._set_filter(filters, slot_filter, slot, var_value,
                                items_model)

        return filters

    @classmethod
    def _set_filter(cls, filters, slot_filter, slot, var_value, items_model):
        factory = cls.get_model('slot_filters').__factory__
        filter_schema, input_schema = \
            cls._get_filter_and_input_schema(slot['engine'], slot_filter)

        if filter_schema is not None and input_schema is not None:
            filter_ = factory.make(items_model, slot_filter, filter_schema)
            filters[filter_] = JsonBuilder.build(var_value, input_schema)

    @classmethod
    def _get_filter_and_input_schema(cls, engine, slot_filter):
        for var in engine['item_type']['available_filters']:
            if var['name'] == slot_filter['property_name']:
                if slot_filter['type_id'] == 'item_property_value' or \
                        slot_filter['type_id'] == 'item_property_value_index':
                    input_schema = {
                        'type': 'array',
                        'items': {
                            'type': 'string'
                        }
                    }

                elif var['schema'].get('type') != 'array':
                    input_schema = {'type': 'array', 'items': var['schema']}

                else:
                    input_schema = var['schema']

                filter_schema = var['schema']
                return filter_schema, input_schema

        return None, None

    @classmethod
    async def _get_fallbacks_recos(cls, slot_recos, slot,
                                   input_external_variables, session,
                                   show_details):
        if len(slot_recos['main']) != slot['max_items']:
            for fallback in slot['fallbacks']:
                fallbacks_recos_size = \
                    sum([len(fallback) for fallback in slot_recos['fallbacks']])
                max_items = slot['max_items'] - len(
                    slot_recos['main']) - fallbacks_recos_size
                if max_items == 0:
                    break

                fallback_recos = await cls._get_recos_by_slot(
                    fallback, input_external_variables, session, show_details,
                    max_items)
                all_recos = cls._get_all_slot_recos(slot_recos)
                fallback_recos = cls._unique_recos(fallback_recos, all_recos)
                slot_recos['fallbacks'].append(fallback_recos)

    @classmethod
    def _get_all_slot_recos(cls, slot_recos):
        all_recos = list(slot_recos['main'])
        [
            all_recos.extend(fallback_recos)
            for fallback_recos in slot_recos['fallbacks']
        ]
        return all_recos

    @classmethod
    def _get_all_recos_from_slots(cls, slots):
        recos = []
        for slot in slots:
            for reco in slot['items']:
                reco['type'] = slot['item_type']

            recos.append(slot['items'])
        return recos

    @classmethod
    def _distribute_items(cls, recos_list, random=True):
        total_length = sum([len(recos) for recos in recos_list])
        total_items = []
        initial_pos = 0

        for recos in recos_list:
            if not recos:
                continue

            step = int(total_length / len(recos))

            if random:
                initial_pos = random_.randint(0, step - 1)

            positions = range(initial_pos, total_length, step)
            zip_items_positions = zip(recos, positions)
            total_items.extend(zip_items_positions)

        random_.shuffle(total_items)
        sorted_items = sorted(total_items, key=(lambda each: each[1]))

        return [i[0] for i in sorted_items]

    @classmethod
    def _unique_recos(cls, recos, all_recos):
        unique = list()
        [unique.append(reco) for reco in recos if not all_recos.count(reco)]
        return unique

    @classmethod
    def _build_redirect_response(cls, recos, distribute_items, req):
        slot_idx = req.query.get('slot_idx')
        item_idx = req.query.get('item_idx')

        if item_idx is None:
            message = {
                'message': "Query argument 'item_idx' "\
                           "is mandatory when 'is_redirect' is true."
            }
            return cls._build_response(400, body=cls._pack_obj(message))

        if (slot_idx is None and distribute_items is False):
            message = {
                'message': "Query argument 'slot_idx' "\
                           "is mandatory when 'distribute_items' is false."
            }
            return cls._build_response(400, body=cls._pack_obj(message))

        elif (slot_idx is not None and distribute_items is True):
            message = {
                'message': "Query argument 'slot_idx' "\
                           "can't be setted when 'distribute_items' is true."
            }
            return cls._build_response(400, body=cls._pack_obj(message))

        if distribute_items is True:
            get_item = lambda recos: recos[item_idx]
        else:
            get_item = lambda recos: recos[slot_idx]['items'][item_idx]

        try:
            location = get_item(recos)

        except IndexError:
            return cls._build_response(404)

        else:
            return cls._build_response(302,
                                       headers={'Location': str(location)})

    @classmethod
    def _build_recos_response(cls, recos):
        headers = {'Content-Type': 'application/json'}
        return cls._build_response(200,
                                   body=cls._pack_obj(recos),
                                   headers=headers)
Example #10
0
class SlotsModelBase(AbstractConcreteBase):
    __tablename__ = 'slots'
    __swagger_json__ = get_swagger_json(__file__)

    id = sa.Column(sa.Integer, primary_key=True)
    max_items = sa.Column(sa.Integer, nullable=False)
    name = sa.Column(sa.String(255), nullable=False)

    @declared_attr
    def engine_id(cls):
        return sa.Column(sa.ForeignKey('engines.id'), nullable=False)

    @declared_attr
    def store_id(cls):
        return sa.Column(sa.ForeignKey('stores.id'), nullable=False)

    @declared_attr
    def engine(cls):
        return sa.orm.relationship('EnginesModel')

    @declared_attr
    def slot_variables(cls):
        return sa.orm.relationship('SlotVariablesModel',
                                   uselist=True,
                                   passive_deletes=True)

    @declared_attr
    def slot_filters(cls):
        return sa.orm.relationship('SlotFiltersModel',
                                   uselist=True,
                                   passive_deletes=True)

    @declared_attr
    def fallbacks(cls):
        return sa.orm.relationship(
            'SlotsModel',
            uselist=True,
            remote_side='SlotsModel.id',
            secondary='slots_fallbacks',
            primaryjoin='slots_fallbacks.c.slot_id == SlotsModel.id',
            secondaryjoin='slots_fallbacks.c.fallback_id == SlotsModel.id')

    async def init(self, session, input_=None, **kwargs):
        await super().init(session, input_=input_, **kwargs)
        self._validate_fallbacks(input_)
        self._validate_slot_variables(input_)
        self._validate_slot_filters(input_)

    def _validate_fallbacks(self, input_):
        for fallback in self.fallbacks:
            if fallback.id == self.id:
                raise SwaggerItModelError(
                    "a Engine Manager can't fallback itself", input_)

            if fallback.engine.item_type_id != self.engine.item_type_id:
                raise SwaggerItModelError(
                    "Cannot set a fallback with different items types", input_)

    def _validate_slot_variables(self, input_):
        if self.engine is not None:
            engine = self.engine.todict()
            engine_variables_set = set(
                [var['name'] for var in engine['variables']])
            message = "Invalid slot variable with 'engine_variable_name' attribute value '{}'"
            schema = {'available_variables': engine['variables']}

            for slot_variable in self.slot_variables:
                var_name = slot_variable.engine_variable_name
                if var_name not in engine_variables_set:
                    raise ValidationError(message.format(var_name),
                                          instance=input_,
                                          schema=schema)

    def _validate_slot_filters(self, input_):
        if self.engine is not None:
            engine = self.engine.todict()
            available_filters = engine['item_type']['available_filters']
            available_filters_set = set(
                [filter_['name'] for filter_ in available_filters])
            schema = {'available_filters': available_filters}
            message = "Invalid slot filter with 'property_name' attribute value '{}'"

            for slot_filter in self.slot_filters:
                filter_name = slot_filter.property_name
                if filter_name not in available_filters_set:
                    raise ValidationError(message.format(filter_name),
                                          instance=input_,
                                          schema=schema)

    async def _setattr(self, attr_name, value, session, input_):
        if attr_name == 'engine_id':
            value = {'id': value}
            attr_name = 'engine'

        if attr_name == 'slot_variables':
            for engine_var in value:
                if 'external_variable_id' in engine_var:
                    var = {'id': engine_var.pop('external_variable_id')}
                    engine_var['external_variable'] = var

        await super()._setattr(attr_name, value, session, input_)

    def _format_output_json(self, dict_inst, schema):
        if schema.get('fallbacks') is not False:
            for fallback in dict_inst.get('fallbacks'):
                fallback.pop('fallbacks')
Example #11
0
class EnginesModelBase(AbstractConcreteBase):
    __tablename__ = 'engines'
    __swagger_json__ = get_swagger_json(__file__)
    _jobs = dict()
    __table_args__ = (sa.UniqueConstraint('name', 'store_id'), )

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(255), nullable=False)

    @declared_attr
    def strategy_id(cls):
        return sa.Column(sa.ForeignKey('engine_strategies.id'), nullable=False)

    @declared_attr
    def store_id(cls):
        return sa.Column(sa.ForeignKey('stores.id'), nullable=False)

    @declared_attr
    def item_type_id(cls):
        return sa.Column(sa.ForeignKey('item_types.id'), nullable=False)

    @declared_attr
    def item_type(cls):
        return sa.orm.relationship('ItemTypesModel')

    @declared_attr
    def store(cls):
        return sa.orm.relationship('StoresModel')

    @declared_attr
    def strategy(cls):
        return sa.orm.relationship('EngineStrategiesModel')

    @declared_attr
    def objects(cls):
        return sa.orm.relationship('EngineObjectsModel',
                                   uselist=True,
                                   secondary='engines_objects')

    @property
    def variables(self):
        return self.strategy_instance.get_variables()

    @property
    def strategy_instance(self):
        if not hasattr(self, '_strategy_instance'):
            self._strategy_instance = \
                type(self.strategy).get_instance(self._todict_when_new())

        return self._strategy_instance

    def _todict_when_new(self):
        objects = []
        self_dict = self.todict({'variables': False})
        strategy_dict = self_dict['strategy']

        for obj in self.objects:
            obj_dict = obj.todict()
            obj_dict['strategy'] = strategy_dict
            objects.append(obj_dict)

        self_dict['objects'] = objects
        return self_dict

    async def _setattr(self, attr_name, value, session, input_):
        if attr_name == 'item_type_id':
            value = {'id': value}
            attr_name = 'item_type'

        if attr_name == 'strategy_id':
            value = {'id': value}
            attr_name = 'strategy'

        if attr_name == 'store_id':
            value = {'id': value}
            attr_name = 'store'

        await super()._setattr(attr_name, value, session, input_)

    async def _validate(self, session, input_):
        strategy_class = self.strategy.get_class()

        for obj in self.objects:
            if obj.type not in strategy_class.object_types:
                raise SwaggerItModelError("Invalid object type '{}'".format(
                    obj.type),
                                          instance=input_)

        props_sequence = [('strategy_id', self.strategy.id),
                          ('item_type_id', self.item_type.id),
                          ('store_id', self.store.id)]

        for obj in self.objects:
            for obj_prop_name, self_prop_value in props_sequence:
                value = getattr(obj, obj_prop_name)

                if value is None:
                    setattr(obj, obj_prop_name, self_prop_value)

                elif value != self_prop_value:
                    raise SwaggerItModelError(
                    "Invalid object '{}' with value '{}'. "\
                    "This value must be the same as '{}'".format(
                        obj_prop_name,
                        value,
                        self_prop_value
                    ),
                    instance=input_
                    )

        self.strategy_instance.validate_config()

    async def init(self, session, input_=None, **kwargs):
        for object_ in kwargs.get('objects', []):
            object_['item_type_id'] = kwargs.get('item_type_id',
                                                 self.item_type_id)
            object_['strategy_id'] = kwargs.get('strategy_id',
                                                self.strategy_id)
            object_['store_id'] = kwargs.get('store_id', self.store_id)

        await super().init(session, input_=input_, **kwargs)

    def _format_output_json(self, dict_inst, schema):
        if schema.get('variables') is not False:
            dict_inst['variables'] = self.variables
Example #12
0
class UsersModelBase(AbstractConcreteBase):
    __tablename__ = 'users'
    __swagger_json__ = get_swagger_json(__file__)

    id = sa.Column(sa.String(255), primary_key=True)
    name = sa.Column(sa.String(255), unique=True, nullable=False)
    email = sa.Column(sa.String(255), unique=True, nullable=False)
    password = sa.Column(sa.String(255), nullable=False)
    admin = sa.Column(sa.Boolean, default=False)

    @declared_attr
    def grants(cls):
        return sa.orm.relationship('GrantsModel',
                                   uselist=True,
                                   secondary='users_grants')

    @declared_attr
    def stores(cls):
        return sa.orm.relationship('StoresModel',
                                   uselist=True,
                                   secondary='users_stores')

    @classmethod
    async def authorize(cls, session, authorization, url, method):
        try:
            authorization = b64decode(authorization).decode()
        except binascii.Error:
            return None
        except UnicodeDecodeError:
            return None

        if not ':' in authorization:
            return None

        user = await cls.get(session, {'id': authorization})
        user = user[0] if user else user
        if user and user.get('admin'):
            session.user = user
            return True

        elif user:
            if method == 'OPTIONS':
                return True

            for grant in user['grants']:
                grant_uri = grant['uri']['uri']
                if (grant_uri == url or re.match(grant_uri, url)) \
                        and grant['method']['method'].lower() == method.lower():
                    session.user = user
                    return True

            return False

    @classmethod
    async def insert(cls, session, objs, commit=True, todict=True):
        objs = cls._to_list(objs)
        await cls._set_objs_ids_and_grant(objs, session)
        return await type(cls).insert(cls, session, objs, commit, todict)

    @classmethod
    async def _set_objs_ids_and_grant(cls, objs, session):
        objs = cls._to_list(objs)

        patch_method = await cls.get_model('methods').get(
            session, ids={'method': 'patch'}, todict=False)
        if not patch_method:
            patch_method = await cls.get_model('methods').insert(
                session, [{
                    'method': 'patch'
                }], todict=False)
        patch_method = patch_method[0]

        get_method = await cls.get_model('methods').get(session,
                                                        ids={'method': 'get'},
                                                        todict=False)
        if not get_method:
            get_method = await cls.get_model('methods').insert(
                session, [{
                    'method': 'get'
                }], todict=False)
        get_method = get_method[0]

        for obj in objs:
            new_grants = []
            user_uri = '/users/{}'.format(obj['email'])

            uri = await cls.get_model('uris').get(session,
                                                  ids={'uri': user_uri},
                                                  todict=False)
            if not uri:
                uri = await cls.get_model('uris').insert(session,
                                                         [{
                                                             'uri': user_uri
                                                         }],
                                                         todict=False)
            uri = uri[0]

            grant = await cls.get_model('grants').get(
                session, {
                    'uri_id': uri.id,
                    'method_id': patch_method.id
                },
                todict=False)
            if grant:
                grant = grant[0].todict()
            else:
                grant = {
                    'uri_id': uri.id,
                    'method_id': patch_method.id,
                    '_operation': 'insert'
                }
            new_grants.append(grant)

            grant = await cls.get_model('grants').get(
                session, {
                    'uri_id': uri.id,
                    'method_id': get_method.id
                },
                todict=False)
            if grant:
                grant = grant[0].todict()
            else:
                grant = {
                    'uri_id': uri.id,
                    'method_id': get_method.id,
                    '_operation': 'insert'
                }
            new_grants.append(grant)

            obj['id'] = '{}:{}'.format(obj['email'], obj['password'])
            grants = obj.get('grants', [])
            grants.extend(new_grants)
            obj['grants'] = grants

    @classmethod
    async def update(cls,
                     session,
                     objs,
                     commit=True,
                     todict=True,
                     ids=None,
                     ids_keys=None):
        if not ids:
            ids = []
            objs = cls._to_list(objs)
            for obj in objs:
                id_ = obj.get('id')
                email = obj.get('email')
                if id_ is not None:
                    ids.append({'id': id_})
                    ids_keys = ('id', )
                elif email is not None:
                    ids.append({'email': email})
                    ids_keys = ('email', )

        insts = await type(cls).update(cls,
                                       session,
                                       objs,
                                       commit=False,
                                       todict=False,
                                       ids=ids,
                                       ids_keys=ids_keys)
        cls._set_insts_ids(insts)

        if commit:
            await session.commit()
        return cls._build_todict_list(insts) if todict else insts

    @classmethod
    def _set_insts_ids(cls, insts):
        insts = cls._to_list(insts)
        for inst in insts:
            inst.id = '{}:{}'.format(inst.email, inst.password)
Example #13
0
class MethodsModelBase(AbstractConcreteBase):
    __tablename__ = 'methods'
    __swagger_json__ = get_swagger_json(__file__, 'methods_swagger.json')

    id = sa.Column(sa.Integer, primary_key=True)
    method = sa.Column(sa.String(10), unique=True, nullable=False)
Example #14
0
class URIsModelBase(AbstractConcreteBase):
    __tablename__ = 'uris'
    __swagger_json__ = get_swagger_json(__file__, 'uris_swagger.json')

    id = sa.Column(sa.Integer, primary_key=True)
    uri = sa.Column(sa.String(255), unique=True, nullable=False)
Example #15
0
class EngineObjectsModelBase(AbstractConcreteBase):
    __tablename__ = 'engine_objects'
    __swagger_json__ = get_swagger_json(__file__)
    __table_args__ = (sa.UniqueConstraint('name', 'type', 'store_id'), )

    id = sa.Column(sa.Integer, primary_key=True)
    type = sa.Column(sa.String(255), nullable=False)
    name = sa.Column(sa.String(255), nullable=False)
    configuration_json = sa.Column(sa.Text, nullable=False)

    @declared_attr
    def strategy_id(cls):
        return sa.Column(sa.ForeignKey('engine_strategies.id'), nullable=False)

    @declared_attr
    def store_id(cls):
        return sa.Column(sa.ForeignKey('stores.id'), nullable=False)

    @declared_attr
    def item_type_id(cls):
        return sa.Column(sa.ForeignKey('item_types.id'), nullable=False)

    @declared_attr
    def strategy(cls):
        return sa.orm.relationship('EngineStrategiesModel')

    @declared_attr
    def item_type(cls):
        return sa.orm.relationship('ItemTypesModel')

    @declared_attr
    def store(cls):
        return sa.orm.relationship('StoresModel')

    @property
    def configuration(self):
        if not hasattr(self, '_configuration'):
            self._configuration = ujson.loads(self.configuration_json)
        return self._configuration

    async def _setattr(self, attr_name, value, session, input_):
        if attr_name == 'configuration':
            value = ujson.dumps(value)
            attr_name = 'configuration_json'

        if attr_name == 'strategy_id':
            value = {'id': value}
            attr_name = 'strategy'

        await super()._setattr(attr_name, value, session, input_)

    def _format_output_json(self, dict_inst, schema):
        if schema.get('configuration') is not False:
            dict_inst.pop('configuration_json')
            dict_inst['configuration'] = self.configuration

    async def _validate(self, session, input_):
        # disable validation when the operation was did by the engine model
        if (isinstance(input_, list) and 'objects' in input_[0]) or \
                (isinstance(input_, dict) and 'objects' in input_):
            return

        strategy_class = self.strategy.get_class()

        if self.type not in strategy_class.object_types:
            raise SwaggerItModelError("Invalid object type '{}'".format(
                self.type),
                                      instance=input_)

        object_schema = strategy_class.configuration_schema['properties'][
            self.type]

        if 'definitions' in strategy_class.configuration_schema:
            object_schema['definitions'] = strategy_class.configuration_schema[
                'definitions']

        jsonschema.validate(self.configuration, object_schema)
Example #16
0
 def _set_items_metaschema_route(self, swagger_doc_url):
     self.items_metaschema = \
         get_swagger_json(__file__, 'item_types/store_items_metaschema.json')
     path = '/doc/items_metaschema.json'
     handler = self._set_handler_decorator(self._get_items_metaschema)
     self._set_route(path, 'GET', handler)