예제 #1
0
파일: cli.py 프로젝트: theosotr/apimas
    def construct_cli_auth(self, instance, spec, loc, context):
        """
        Constructor of `.cli_auth` predicate.

        It adds an additional option to all commands in order to specify
        credentials to interact with the API.

        There are multiple authentication model that can be supported according
        to the specification. However, credentials must be stored in a
        configuration file of specific format (`JSON` and `YAML` are supported
        at the momment).
        """
        if self.ADAPTER_CONF not in instance:
            raise doc.DeferConstructor
        auth_format = doc.doc_get(spec, ('.auth_format', 'format'))
        auth_modes = self.get_structural_elements(spec)
        auth_schema = {}
        for auth_mode in auth_modes:
            schema = doc.doc_get(spec, (auth_mode, ))
            auth_schema[auth_mode] = schema
        commands = doc.doc_get(instance, (self.ADAPTER_CONF, 'actions'))
        assert commands, ('Loc: %s, commands have not been constructed yet.' %
                          (str(loc)))
        credential_option = click.option('--credentials',
                                         required=True,
                                         type=Credentials(
                                             schema=auth_schema,
                                             file_type=auth_format))
        for command in commands:
            credential_option(command)
        return instance
예제 #2
0
    def construct_drf_collection(self, instance, spec, loc, context):
        """
        Constructor for `.drf_collection` predicate.

        It generates the required, `Serializer` class, and `ViewSet` class
        based on the field schema, actions, permissions and additional
        configuation (filter_fields, mixins) as specified on spec.
        """
        parent = context.get('parent_name')
        constructed = context.get('constructed')
        if '.collection' not in constructed:
            raise doc.DeferConstructor
        field_schema = doc.doc_get(instance, ('*',))
        actions = doc.doc_get(instance, ('actions', self.ADAPTER_CONF)) or []
        model = self._get_or_import_model(parent, loc + ('model',),
                                          context.get('top_spec'))
        model_serializers = spec.pop('model_serializers', [])
        extra_serializers = spec.pop('serializers', [])
        serializer = self.generate_serializer(
            field_schema, parent, model=model,
            model_serializers=model_serializers,
            extra_serializers=extra_serializers)
        kwargs = {k: v for k, v in spec.iteritems() if k != 'model'}
        permissions = self.get_permissions(parent, context.get('top_spec'))
        view = generate_view(parent, serializer, model, actions=actions,
                             permissions=permissions, **kwargs)
        instance[self.ADAPTER_CONF] = view
        self.serializers[parent] = serializer
        self.views[parent] = view
        return instance
예제 #3
0
파일: clients.py 프로젝트: theosotr/apimas
    def construct_field(self, instance, spec, loc, context):
        """
        Constructor of `.field` predicate.

        It constructs a dictionary corresponding to a cerberus validation
        schema along with all rules based on spec.
        """
        def default(instance, spec, loc, context, **kwargs):
            return instance

        parent_name = context.get('parent_name')
        nested_structures = {'.struct', '.structarray'}
        field_type = self.extract_type(instance)
        if not field_type:
            raise ex.ApimasException(
                'You have to specify field type for field `%s`' %
                (parent_name))
        self.init_adapter_conf(instance)
        if field_type in nested_structures:
            return self.construct_nested_field(instance, spec, loc, context,
                                               field_type)
        method_name = '_add_' + field_type[1:] + '_params'
        params = doc.doc_get(instance, (field_type, ))
        return getattr(self, method_name, default)(instance, spec, loc,
                                                   context, **params)
예제 #4
0
파일: cli.py 프로젝트: theosotr/apimas
    def construct_cli_option(self, instance, spec, loc, context):
        """
        Constructor for '.cli_option' predicate.

        It constructs a dictionary keyed by option name which contains
        all required keyword arguments for `click.option()` constructor.
        """
        parent_name = context.get('parent_name')
        extra_params = {'.ref': self._add_ref_params}
        if instance == SKIP:
            return instance
        predicate_type = self.extract_type(instance)
        option_name = doc.doc_get(spec, ('option_name', )) or parent_name
        if predicate_type == '.struct':
            return self.construct_struct_option(instance, parent_name, spec,
                                                loc, option_name)
        instance = self.init_adapter_conf(instance, initial={option_name: {}})
        kwargs = {
            'type':
            self.construct_option_type(instance, spec, loc, context,
                                       predicate_type)
        }
        extra = extra_params.get(predicate_type)
        if extra:
            kwargs.update(extra(instance, spec, loc, context))
        if '.required' in instance:
            kwargs.update({'required': True})
        instance[self.ADAPTER_CONF][option_name] = kwargs
        return instance
예제 #5
0
파일: cli.py 프로젝트: theosotr/apimas
    def construct_struct_option(self, instance, parent_name, spec, loc,
                                option_name):
        """
        Constructor for `.struct` predicate.

        This field corresponds to a python `dict` with all options and their
        keyword parameters associated with this field.

        A struct is consisted of all options of its field schema.
        Example:

        A struct named 'cart' which incorporates fields `id` and `products`
        corresponds to the following options:

        * --cart-id
        * --cart-products
        """
        option_kwargs = {}
        self.init_adapter_conf(instance)
        for _, schema in doc.doc_get(instance, ('.struct', )).iteritems():
            if schema == SKIP:
                continue
            for nested, params in schema.get(self.ADAPTER_CONF).iteritems():
                option_kwargs.update({option_name + '-' + nested: params})
                self.struct_map[option_name + '-' + nested] = (
                    parent_name, ) + self.struct_map.get(nested, (nested, ))
        instance[self.ADAPTER_CONF].update(option_kwargs)
        return instance
예제 #6
0
파일: clients.py 프로젝트: theosotr/apimas
    def partial_validate(self, data, raise_exception=True, schema=None):
        """
        Validates data that are going to be sent for a partial update of a
        resource.

        Construct a cerberus schema validator by taking into consideration
        only the fields that are included in the request.

        :param raise_exception: True if an exception should be raised when
        validation fails.
        """
        schema = schema or self.validation_schema
        cerberus_paths = to_cerberus_paths(data)
        validated_subdocs = self._validate_subdata(data, schema,
                                                   raise_exception)
        partial_schema_paths = {
            path: doc.doc_get(schema, path.split('/'))
            for path in cerberus_paths
        }
        partial_schema = doc.doc_from_ns(partial_schema_paths)
        validator = ApimasValidator(partial_schema)
        is_valid = validator.validate(data)
        if raise_exception and not is_valid:
            raise ex.ApimasClientException(validator.errors)
        for k, v in validated_subdocs.iteritems():
            doc.doc_set(validator.document, k, v)
        return validator.document
예제 #7
0
    def get_permissions(self, collection, top_spec):
        """
        It constructs permissions rules for every collection.

        Typically, permission rules are provided at a global scope. Then,
        this method, actually matches all permissions rules which are applied
        to a specific collection and then it returns all permissions that
        are compatible and apply on the collection.
        """
        permission_path = ('.endpoint', 'permissions')
        nu_columns = 6
        permission_doc = {}
        permissions = doc.doc_get(top_spec, permission_path) or []
        permissions = [[doc.parse_pattern(segment) for segment in row]
                       for row in permissions]
        for rule in permissions:
            doc.doc_set(permission_doc, rule[:-1], rule[-1])
        patterns = [[collection], [doc.ANY], [doc.ANY], [doc.ANY],
                    [doc.ANY]]
        matches = list(doc.doc_match_levels(
            permission_doc, patterns,
            expand_pattern_levels=range(nu_columns)))
        if not matches:
            return None
        return map((lambda x: x[1:]), matches)
예제 #8
0
 def construct_collection(self, instance, spec, loc, context):
     self.init_adapter_conf(instance)
     field_schema = doc.doc_get(instance, ('*', ))
     assert len(loc) >= 3
     if not field_schema:
         raise ex.ApimasException(
             'A collection must define its field schema.'
             ' Empty collection found: %s' % (loc[-2]))
     return instance
예제 #9
0
 def get_constructor_params(self, spec, loc, params):
     """
     Get constructor params for all the constructors that represent a
     structure, e.g. `.struct`, `.collection`, etc.
     """
     for structure in self.STRUCTURES.keys():
         struct_doc = doc.doc_get(spec, loc[:-1]) or {}
         structure_params = doc.doc_get(
             struct_doc, (self.STRUCTURES[structure],)) or {}
         onmodel = structure_params.get('onmodel', True)
         if structure in struct_doc and onmodel:
             if structure == '.collection':
                 params.append((structure, {'model': self.models[loc[-2]]}))
                 continue
             source = structure_params.get('source')
             params.append((structure, {'source': source or loc[-2]}))
     if loc[:-1]:
         return self.get_constructor_params(spec, loc[:-1], params)
     return params
예제 #10
0
 def _get_or_import_model(self, collection, model_path, top_spec):
     """
     This function checks if a model of a collection is already specified
     and imported and retrieves it. If this is not the case, then it
     imports it and retrieves it.
     """
     if collection not in self.models:
         model = utils.import_object(
             doc.doc_get(top_spec, model_path))
         self.models[collection] = model
     else:
         model = self.models[collection]
     return model
예제 #11
0
 def generate_nested_drf_field(self, instance, name, predicate_type, model,
                               onmodel=True, **kwargs):
     """
     Generate a nested drf field, which is actually a `Serializer` class.
     """
     kwargs.update(self.get_default_properties(predicate_type, kwargs))
     field_schema = doc.doc_get(instance, (predicate_type,))
     many = predicate_type == '.structarray'
     model_serializers = kwargs.pop('model_serializers', [])
     extra_serializers = kwargs.pop('serializers', [])
     serializer = self.generate_serializer(
         field_schema, name, onmodel=onmodel,
         model_serializers=model_serializers,
         extra_serializers=extra_serializers, model=model)
     return serializer(many=many, **kwargs)
예제 #12
0
    def _classify_fields(self, field_schema):
        """
        Seperates the model fields fro the non-model fields.

        It also returns a dictionary of instance sources (if they are exist)
        for the non-model fields.
        """
        model_fields = {}
        extra_fields = {}
        instance_sources = {}
        for field_name, properties in field_schema.iteritems():
            onmodel = doc.doc_get(properties, ('.drf_field', 'onmodel'))
            if onmodel is None:
                onmodel = True
            field_path = (self.ADAPTER_CONF, 'field')
            instance_path = (self.ADAPTER_CONF, 'source')
            field = doc.doc_get(properties, field_path)
            if onmodel:
                model_fields[field_name] = field
            else:
                extra_fields[field_name] = field
            instance_sources[field_name] = doc.doc_get(properties,
                                                       instance_path)
        return model_fields, extra_fields, instance_sources
예제 #13
0
 def apply(self):
     """
     Create django rest views based on the constructed adapter spec.
     """
     if not self.adapter_spec:
         raise utils.DRFAdapterException(
             'Cannot apply an empty adapter specification')
     structural_elements = self.get_structural_elements(self.adapter_spec)
     api = structural_elements[0]
     router = routers.DefaultRouter()
     for collection, spec in doc.doc_get(
             self.adapter_spec, (api,)).iteritems():
         view = spec.get(self.ADAPTER_CONF)
         router.register(collection, view, base_name=collection)
     self.urls = url(r'^' + api + '/', include(router.urls))
예제 #14
0
파일: cli.py 프로젝트: theosotr/apimas
    def construct_cli_commands(self, instance, spec, loc, context):
        """
        Constructor for '.cli_commands' predicate.

        Gets all commands corresponding to actions and attaches the
        appropriate options to them based on field schema.
        """
        parent_name = context.get('parent_name')
        instance = self.init_adapter_conf(instance, initial={'actions': set()})
        commands = doc.doc_get(instance, ('actions', self.ADAPTER_CONF)) or {}
        for action, command in commands.iteritems():
            command = self.construct_command(instance, parent_name, spec, loc,
                                             action, command)
            instance[self.ADAPTER_CONF]['actions'].add(command)
        return instance
예제 #15
0
파일: clients.py 프로젝트: theosotr/apimas
    def construct_collection(self, instance, spec, loc, context):
        """
        Constructor for `.collection` predicate.

        This constructor aims to aggregate the cerberus validation schemas
        for every single field defined by the collection.
        """
        instance = super(self.__class__,
                         self).construct_collection(instance, spec, loc,
                                                    context)
        self.init_adapter_conf(instance)
        schema = {
            field_name: schema.get(self.ADAPTER_CONF, {})
            for field_name, schema in doc.doc_get(instance, (
                '*', )).iteritems()
        }
        instance[self.ADAPTER_CONF] = schema
        return instance
예제 #16
0
파일: clients.py 프로젝트: theosotr/apimas
    def apply(self):
        """
        Apply generated cerberus specification and create `ApimasClient`
        objects for every resource defined in the specification.
        """
        if not self.adapter_spec:
            raise ex.ApimasException(
                'Cannot create clients from an empty spec')

        structural_elements = self.get_structural_elements(self.adapter_spec)
        assert len(structural_elements) == 1
        for collection, spec in doc.doc_get(
                self.adapter_spec, (structural_elements[0], )).iteritems():
            schema = spec.get(self.ADAPTER_CONF, {})
            endpoint = urljoin(
                self.root_url,
                TRAILING_SLASH.join([structural_elements[0], collection]))
            endpoint += TRAILING_SLASH
            self.clients[collection] = ApimasClient(endpoint, schema)
예제 #17
0
파일: cli.py 프로젝트: theosotr/apimas
 def construct_command(self, instance, command_name, spec, loc, action,
                       command):
     """
     Construct command's options for a specific collection according to the
     `APIMAS` specification.
     """
     field_schema = doc.doc_get(instance, ('*', ))
     for field_name, spec in field_schema.iteritems():
         if spec == SKIP:
             continue
         for option_name, params in spec.get(self.ADAPTER_CONF).iteritems():
             option_constructor = self.OPTION_CONSTRUCTORS[action]
             if not self.option_allowed(action, spec, option_constructor):
                 continue
             command = option_constructor(option_name, params)(command)
             path = (field_name,) if '.struct' not in spec\
                 else self.struct_map[option_name]
             command.register_option_mapping(option_name.replace('-', '_'),
                                             path)
     return base_group.command(name=loc[-2] + '-' + action)(command)
예제 #18
0
 def validate_ref(self, instance, name, loc, top_spec, source):
     """
     Validates that the referenced field is a foreign key to the same
     django model table as the model defined in the referenced collection
     of spec. Otherwise, an exception with explanatory message is raised.
     """
     root_loc = loc[0:1]
     ref = doc.doc_get(instance, ('.ref', 'to'))
     django_conf = self.get_constructor_params(top_spec, loc, [])
     model = self.extract_model(source or name, django_conf)
     auto = True
     try:
         model_field = model._meta.get_field(source or name)
         path = root_loc + (ref, '.drf_collection', 'model')
         ref_model = self._get_or_import_model(ref, path, top_spec)
         model_attr = _validate_relational_field(
             name, ref_model, model_field)
     except FieldDoesNotExist:
         auto = False
         model_attr = _validate_model_attribute(name, model,
                                                source or name)
     return model_attr, model, auto
예제 #19
0
    def construct_property(self, instance, spec, loc, context, property_name):
        """
        Constuctor for predicates that indicate a property of a field,
        e.g. nullable, readonly, required, etc.

        This constructor generates the corresponding spec syntax. However,
        it requires field to be initialized, otherwise, construction is
        defered.
        """
        if property_name not in self.PROPERTY_MAPPING:
            raise ex.ApimasException('Unknown property name %s' %
                                     (property_name))
        constructed = context.get('constructed')
        predicate_type = self.extract_type(instance)
        if predicate_type not in constructed:
            raise doc.DeferConstructor

        if predicate_type in self.SKIP_FIELDS:
            return instance
        field_schema = doc.doc_get(instance, (self.ADAPTER_CONF, ))
        field_schema.update(
            {self.PROPERTY_MAPPING.get(property_name, property_name): True})
        return instance
예제 #20
0
    def default_field_constructor(self, instance, spec, loc, context,
                                  predicate_type):
        """
        A common constructor for the drf fields.

        There are two cases:
        * If the field is a model field, then it does not initialize a
          `serializers.Field` object, but it stores all its properties in
          dictionary in order to be initialized later from the serializer.
        * If the field is a non-model field or its type is either `.struct`
          or `.structarry`, then the corresponding `serializers.Field` is
          contructed.

        Moreover, this method checks if the field conforms to the model
        configuations before being constructed.
        """
        model, automated = self.validate_model_configuration(
            instance, spec, loc, context, predicate_type)
        path = (self.ADAPTER_CONF,)
        instance_source = spec.pop('instance_source', None)
        onmodel = spec.get('onmodel', True)
        if instance_source and onmodel:
            raise utils.DRFAdapterException(
                'You don\'t have to specify `instance_source` if'
                ' `onmodel` is set')
        field_kwargs = {k: v for k, v in spec.iteritems() if k != 'onmodel'}
        field_kwargs.update(doc.doc_get(instance, path) or {})
        if predicate_type == '.ref':
            field_kwargs.update(self._get_ref_params(
                instance, loc, context.get('top_spec'), onmodel and automated,
                field_kwargs))
        doc.doc_set(instance, (self.ADAPTER_CONF, 'source'), instance_source)
        drf_field = self._generate_field(
            instance, context.get('parent_name'), predicate_type, model,
            automated and onmodel, **field_kwargs)
        doc.doc_set(instance, (self.ADAPTER_CONF, 'field'), drf_field)
        return instance
예제 #21
0
파일: clients.py 프로젝트: theosotr/apimas
    def construct_nested_field(self,
                               instance,
                               spec,
                               loc,
                               context,
                               field_type=None):
        """
        Constructor for predicates that include nested schemas. Typically,
        `.struct` and `.structarray` predicates are included in this category
        of fields.

        This constructor generates the corresponding cerberus syntax for having
        a `list` of dicts or a `dict` in accordance to the aforementioned
        structures.
        """
        bound_field = {
            '.struct': lambda x: {
                'type': 'dict',
                'schema': x
            },
            '.structarray': lambda x: {
                'type': 'list',
                'schema': {
                    'type': 'dict',
                    'schema': x
                }
            }
        }
        params = doc.doc_get(instance, (field_type, ))
        field_schema = {
            field_name: schema.get(self.ADAPTER_CONF, {})
            for field_name, schema in params.iteritems()
        }
        instance[self.ADAPTER_CONF].update(
            bound_field[field_type](field_schema))
        return instance
예제 #22
0
def get_ref_collections(spec, collection):
    structural_element = get_structural_element(spec)
    loc = (structural_element, collection, '*')
    field_schema = doc.doc_get(spec, loc)
    refs = get_refs(field_schema, spec)
    return refs
예제 #23
0
def get_required_fields(spec, collection):
    structural_element = get_structural_element(spec)
    loc = (structural_element, collection, '*')
    field_schema = doc.doc_get(spec, loc)
    return filter_field_schema(field_schema, included=['.required'])
예제 #24
0
def get_fields(spec, collection, excluded=None, included=None):
    structural_element = get_structural_element(spec)
    loc = (structural_element, collection, '*')
    field_schema = doc.doc_get(spec, loc)
    return filter_field_schema(field_schema, excluded, included)
예제 #25
0
def action_exists(spec, collection, action):
    structural_element = get_structural_element(spec)
    loc = (structural_element, collection, 'actions')
    actions = doc.doc_get(spec, loc) or {}
    return action in actions
예제 #26
0
파일: cli.py 프로젝트: theosotr/apimas
 def _add_ref_params(self, instance, spec, loc, context):
     many = doc.doc_get(instance, ('.ref', 'many'))
     return {'multiple': True} if many else {}