Example #1
0
    def _fetch_schema(self):
        schema = self.session.get(self._schema_url).json(
            cls=PotionJSONSchemaDecoder,
            referrer=self._schema_url,
            client=self)

        # NOTE these should perhaps be definitions in Flask-Potion
        for name, resource_schema in schema["properties"].items():
            resource = self.resource_factory(name, resource_schema)
            setattr(self, upper_camel_case(name), resource)
Example #2
0
    def _fetch_schema(self):
        schema = self.session \
            .get(self._schema_url) \
            .json(cls=PotionJSONSchemaDecoder,
                  referrer=self._schema_url,
                  client=self)

        # NOTE these should perhaps be definitions in Flask-Potion
        for name, resource_schema in schema['properties'].items():
            resource = self.resource_factory(name, resource_schema)
            setattr(self, upper_camel_case(name), resource)
Example #3
0
    def resource_factory(self, name, schema, resource_cls=None):
        """
        Registers a new resource with a given schema. The schema must not have any unresolved references
        (such as `{"$ref": "#"}` for self-references, or otherwise). A subclass of :class:`Resource`
        may be provided to add specific functionality to the resulting :class:`Resource`.

        :param str name:
        :param dict schema:
        :param Resource resource_cls: a subclass of :class:`Resource` or None
        :return: The new :class:`Resource`.
        """
        cls = type(
            str(upper_camel_case(name)),
            (resource_cls or Resource, collections.abc.MutableMapping),
            {"__doc__": schema.get("description", "")},
        )

        cls._schema = schema
        cls._client = self
        cls._links = links = {}

        for link_schema in schema["links"]:
            link = Link(
                self,
                rel=link_schema["rel"],
                href=link_schema["href"],
                method=link_schema["method"],
                schema=link_schema.get("schema", None),
                target_schema=link_schema.get("targetSchema", None),
            )

            # Set Resource._self, etc. for the special methods as they are managed by the Resource class
            if link.rel in ("self", "instances", "create", "update",
                            "destroy"):
                setattr(cls, "_{}".format(link.rel), link)
            links[link.rel] = link

            if link.rel != "update":  # 'update' is a special case because of MutableMapping.update()
                setattr(cls, snake_case(link.rel), link)

        # TODO routes (instance & non-instance)

        for property_name, property_schema in schema.get("properties",
                                                         {}).items():
            # skip $uri and $id as these are already implemented in Resource and overriding them causes unnecessary
            # fetches.
            if property_name.startswith("$"):
                continue

            if property_schema.get("readOnly", False):
                # TODO better error message. Raises AttributeError("can't set attribute")
                setattr(
                    cls,
                    property_name,
                    property(
                        fget=partial((lambda name, obj: getitem(obj, name)),
                                     property_name),
                        doc=property_schema.get("description", None),
                    ),
                )
            else:
                setattr(
                    cls,
                    property_name,
                    property(
                        fget=partial((lambda name, obj: getitem(obj, name)),
                                     property_name),
                        fset=partial((lambda name, obj, value: setitem(
                            obj, name, value)), property_name),
                        fdel=partial((lambda name, obj: delitem(obj, name)),
                                     property_name),
                        doc=property_schema.get("description", None),
                    ),
                )

        root = None
        if "instances" in links:
            root = cls._instances.href
        elif "self" in links:
            root = cls._self.href[:cls._self.href.rfind("/")]
        else:
            root = self._root_path + "/" + name.replace("_", "-")

        self._resources[root] = cls
        return cls
Example #4
0
    def resource_factory(self, name, schema, resource_cls=None):
        """
        Registers a new resource with a given schema. The schema must not have any unresolved references
        (such as `{"$ref": "#"}` for self-references, or otherwise). A subclass of :class:`Resource`
        may be provided to add specific functionality to the resulting :class:`Resource`.

        :param str name:
        :param dict schema:
        :param Resource resource_cls: a subclass of :class:`Resource` or None
        :return: The new :class:`Resource`.
        """
        cls = type(str(upper_camel_case(name)),
                   (resource_cls or Resource, collections.MutableMapping),
                   {'__doc__': schema.get('description', '')})

        cls._schema = schema
        cls._client = self
        cls._links = links = {}

        for link_schema in schema['links']:
            link = Link(self,
                        rel=link_schema['rel'],
                        href=link_schema['href'],
                        method=link_schema['method'],
                        schema=link_schema.get('schema', None),
                        target_schema=link_schema.get('targetSchema', None))

            # Set Resource._self, etc. for the special methods as they are managed by the Resource class
            if link.rel in ('self', 'instances', 'create', 'update',
                            'destroy'):
                setattr(cls, '_{}'.format(link.rel), link)
            links[link.rel] = link

            if link.rel != 'update':  # 'update' is a special case because of MutableMapping.update()
                setattr(cls, snake_case(link.rel), link)

        # TODO routes (instance & non-instance)

        for property_name, property_schema in schema['properties'].items():
            # skip $uri and $id as these are already implemented in Resource and overriding them causes unnecessary
            # fetches.
            if property_name.startswith('$'):
                continue

            if property_schema.get('readOnly', False):
                # TODO better error message. Raises AttributeError("can't set attribute")
                setattr(
                    cls, property_name,
                    property(fget=partial(
                        (lambda name, obj: getitem(obj, name)), property_name),
                             doc=property_schema.get('description', None)))
            else:
                setattr(
                    cls, property_name,
                    property(fget=partial(
                        (lambda name, obj: getitem(obj, name)), property_name),
                             fset=partial((lambda name, obj, value: setitem(
                                 obj, name, value)), property_name),
                             fdel=partial(
                                 (lambda name, obj: delitem(obj, name)),
                                 property_name),
                             doc=property_schema.get('description', None)))

        root = None
        if 'instances' in links:
            root = cls._instances.href
        elif 'self' in links:
            root = cls._self.href[:cls._self.href.rfind('/')]
        else:
            root = self._root_path + '/' + name.replace('_', '-')

        self._resources[root] = cls
        return cls
Example #5
0
    def resource_factory(self, name, schema, resource_cls=None):
        """
        Registers a new resource with a given schema. The schema must not have any unresolved references
        (such as `{"$ref": "#"}` for self-references, or otherwise). A subclass of :class:`Resource`
        may be provided to add specific functionality to the resulting :class:`Resource`.

        :param str name:
        :param dict schema:
        :param Resource resource_cls: a subclass of :class:`Resource` or None
        :return: The new :class:`Resource`.
        """
        cls = type(str(upper_camel_case(name)), (resource_cls or Resource, collections.MutableMapping), {
            '__doc__': schema.get('description', '')
        })

        cls._schema = schema
        cls._client = self
        cls._links = links = {}

        for link_schema in schema['links']:
            link = Link(self,
                        rel=link_schema['rel'],
                        href=link_schema['href'],
                        method=link_schema['method'],
                        schema=link_schema.get('schema', None),
                        target_schema=link_schema.get('targetSchema', None))

            # Set Resource._self, etc. for the special methods as they are managed by the Resource class
            if link.rel in ('self', 'instances', 'create', 'update', 'destroy'):
                setattr(cls, '_{}'.format(link.rel), link)
            links[link.rel] = link

            if link.rel != 'update':  # 'update' is a special case because of MutableMapping.update()
                setattr(cls, snake_case(link.rel), link)

        # TODO routes (instance & non-instance)

        for property_name, property_schema in schema.get('properties', {}).items():
            # skip $uri and $id as these are already implemented in Resource and overriding them causes unnecessary
            # fetches.
            if property_name.startswith('$'):
                continue

            if property_schema.get('readOnly', False):
                # TODO better error message. Raises AttributeError("can't set attribute")
                setattr(cls,
                        property_name,
                        property(fget=partial((lambda name, obj: getitem(obj, name)), property_name),
                                 doc=property_schema.get('description', None)))
            else:
                setattr(cls,
                        property_name,
                        property(fget=partial((lambda name, obj: getitem(obj, name)), property_name),
                                 fset=partial((lambda name, obj, value: setitem(obj, name, value)), property_name),
                                 fdel=partial((lambda name, obj: delitem(obj, name)), property_name),
                                 doc=property_schema.get('description', None)))

        root = None
        if 'instances' in links:
            root = cls._instances.href
        elif 'self' in links:
            root = cls._self.href[:cls._self.href.rfind('/')]
        else:
            root = self._root_path + '/' + name.replace('_', '-')

        self._resources[root] = cls
        return cls
Example #6
0
    def _fetch_schema(self, cache_schema=False, creds_file=None):
        self._cached_schema = {}
        creds_fp = os.path.expanduser(
            '~/.onecodex') if creds_file is None else creds_file

        if os.path.exists(creds_fp):
            creds = json.load(open(creds_fp, 'r'))
        else:
            creds = {}

        schema = None
        serialized_schema = None
        if cache_schema:
            # Determine if we need to update
            schema_update_needed = True
            last_update = creds.get('schema_saved_at')
            if last_update is not None:
                last_update = datetime.strptime(last_update, self.DATE_FORMAT)
                time_diff = datetime.now() - last_update
                schema_update_needed = time_diff.days > self.SCHEMA_SAVE_DURATION

            if not schema_update_needed:
                # get the schema from the credentials file (as a string)
                serialized_schema = creds.get('schema')

        if serialized_schema is not None:
            # Catch schema caching issues and fall back to remote URL
            try:
                base_schema = serialized_schema.pop(self._schema_url)
                schema = json.loads(base_schema,
                                    cls=PotionJSONSchemaDecoder,
                                    referrer=self._schema_url,
                                    client=self)

                for route, route_schema in serialized_schema.items():
                    object_schema = json.loads(route_schema,
                                               cls=PotionJSONSchemaDecoder,
                                               referrer=self._schema_url,
                                               client=self)
                    self._cached_schema[route] = object_schema
            except KeyError:  # Caches issue with schema_url not existing
                pass

        if schema is None:
            # if the schema wasn't cached or if it was expired, get it anew
            schema = self.session.get(self._schema_url).json(
                cls=PotionJSONSchemaDecoder,
                referrer=self._schema_url,
                client=self)
            if cache_schema:
                # serialize the schemas back out
                creds['schema_saved_at'] = datetime.strftime(
                    datetime.now(), self.DATE_FORMAT)

                # serialize the main schema
                serialized_schema = {}
                serialized_schema[self._schema_url] = json.dumps(
                    schema, cls=PotionJSONEncoder)

                # serialize the object schemas
                for schema_ref in schema['properties'].values():
                    serialized_schema[schema_ref._uri] = json.dumps(
                        schema_ref._properties, cls=PotionJSONEncoder)

                creds['schema'] = serialized_schema
            else:
                if 'schema_saved_at' in creds:
                    del creds['schema_saved_at']
                if 'schema' in creds:
                    del creds['schema']

            # always resave the creds (to make sure we're removing schema if we need to be or
            # saving if we need to do that instead)
            json.dump(creds, open(creds_fp, mode='w'))

        for name, resource_schema in schema['properties'].items():
            class_name = upper_camel_case(name)
            setattr(self, class_name,
                    self.resource_factory(name, resource_schema))