예제 #1
0
    def save(self):
        """Either create or persist changes on this object back to the One Codex server."""
        check_bind(self)

        creating = self.id is None
        if creating and not self.__class__._has_schema_method("create"):
            raise MethodNotSupported("{} do not support creating.".format(self.__class__.__name__))
        if not creating and not self.__class__._has_schema_method("update"):
            raise MethodNotSupported("{} do not support updating.".format(self.__class__.__name__))

        try:
            self._resource.save()
        except HTTPError as e:
            if e.response.status_code == 400:
                err_json = e.response.json().get("errors", [])
                msg = pretty_print_error(err_json)
                raise ServerError(msg)
            elif e.response.status_code == 404:
                action = "creating" if creating else "updating"
                raise MethodNotSupported(
                    "{} do not support {}.".format(self.__class__.__name__, action)
                )
            elif e.response.status_code == 409:
                raise ServerError("This {} object already exists".format(self.__class__.__name__))
            else:
                raise e
예제 #2
0
    def save(self):
        """Either create or persist changes on this object back to the One Codex server."""
        check_bind(self)

        creating = self.id is None
        if creating and not self.__class__._has_schema_method("create"):
            raise MethodNotSupported("{} do not support creating.".format(
                self.__class__.__name__))
        if not creating and not self.__class__._has_schema_method("update"):
            raise MethodNotSupported("{} do not support updating.".format(
                self.__class__.__name__))

        try:
            self._resource.save()
        except HTTPError as e:
            if e.response.status_code == 400:
                err_json = e.response.json().get("errors", [])
                msg = pretty_print_error(err_json)
                raise ServerError(msg)
            elif e.response.status_code == 404:
                action = "creating" if creating else "updating"
                raise MethodNotSupported("{} do not support {}.".format(
                    self.__class__.__name__, action))
            elif e.response.status_code == 409:
                raise ServerError("This {} object already exists".format(
                    self.__class__.__name__))
            else:
                raise e
예제 #3
0
    def delete(self):
        """Delete this object from the One Codex server."""
        check_bind(self)
        if self.id is None:
            raise ServerError("{} object does not exist yet".format(self.__class__.name))
        elif not self.__class__._has_schema_method("destroy"):
            raise MethodNotSupported("{} do not support deletion.".format(self.__class__.__name__))

        try:
            self._resource.delete()
        except HTTPError as e:
            if e.response.status_code == 403:
                raise PermissionDenied("")  # FIXME: is this right?
            else:
                raise e
예제 #4
0
    def delete(self):
        """Delete this object from the One Codex server."""
        check_bind(self)
        if self.id is None:
            raise ServerError("{} object does not exist yet".format(
                self.__class__.name))
        elif not self.__class__._has_schema_method("destroy"):
            raise MethodNotSupported("{} do not support deletion.".format(
                self.__class__.__name__))

        try:
            self._resource.delete()
        except HTTPError as e:
            if e.response.status_code == 403:
                raise PermissionDenied("")  # FIXME: is this right?
            else:
                raise e
예제 #5
0
    def get(cls, uuid):
        """
        Retrieve one specific {classname} object from the server by its UUID
        (unique 16-character id). UUIDs can be found in the web browser's address bar while
        viewing analyses and other objects.

        Parameters
        ----------
        uuid : string
            UUID of the {classname} object to retrieve.

        Returns
        -------
        OneCodexBase | None
            The {classname} object with that UUID or None if no {classname} object could be found.

        Examples
        --------
        >>> api.Samples.get('xxxxxxxxxxxxxxxx')
        <Sample xxxxxxxxxxxxxxxx>
        """.format(classname=cls.__name__)
        check_bind(cls)

        # we're just retrieving one object from its uuid
        try:
            resource = cls._resource.fetch(uuid)
            if isinstance(resource, list):
                # TODO: Investigate why potion .fetch()
                #       method is occassionally returning a list here...
                if len(resource) == 1:
                    resource = resource[0]
                else:
                    raise TypeError("Potion-Client error in fetching resource")
        except HTTPError as e:
            # 404 error means this doesn't exist
            if e.response.status_code == 404:
                return None
            else:
                raise e
        return cls(_resource=resource)
예제 #6
0
    def get(cls, uuid):
        """Retrieve one specific object from the server by its UUID (unique 16-character id). UUIDs
        can be found in the web browser's address bar while viewing analyses and other objects.

        Parameters
        ----------
        uuid : string
            UUID of the object to retrieve.

        Returns
        -------
        OneCodexBase | None
            The object with that UUID or None if no object could be found.

        Examples
        --------
        >>> api.Samples.get('xxxxxxxxxxxxxxxx')
        <Sample xxxxxxxxxxxxxxxx>
        """
        check_bind(cls)

        # we're just retrieving one object from its uuid
        try:
            resource = cls._resource.fetch(uuid)
            if isinstance(resource, list):
                # TODO: Investigate why potion .fetch()
                #       method is occassionally returning a list here...
                if len(resource) == 1:
                    resource = resource[0]
                else:
                    raise TypeError("Potion-Client error in fetching resource")
        except HTTPError as e:
            # 404 error means this doesn't exist
            if e.response.status_code == 404:
                return None
            else:
                raise e
        return cls(_resource=resource)
예제 #7
0
    def where(cls, *filters, **keyword_filters):
        """Retrieve objects (Samples, Classifications, etc.) from the One Codex server.

        Parameters
        ----------
        filters : `object`
            Advanced filters to use (not implemented)
        sort : `str` or `list`, optional
            Sort the results by this field (or list of fields). By default in descending order,
            but if any of the fields start with the special character ^, sort in ascending order.
            For example, sort=['size', '^filename'] will sort by size from largest to smallest and
            filename from A-Z for items with the same size.
        limit : `int`, optional
            Number of records to return. For smaller searches, this can reduce the number of
            network requests made.
        keyword_filters : `str` or `object`
            Filter the results by specific keywords (or filter objects, in advanced usage)

        Examples
        --------
        You can filter objects that are returned locally using a lambda function:

            # returns only samples with a filename ending in '.gz'
            my_samples = Samples.where(filter=lambda s: s.filename.endswith('.gz'))

        Returns
        -------
        `list`
            A list of all objects matching these filters. If no filters are passed, this
            matches all objects.
        """
        check_bind(cls)

        # do this here to avoid passing this on to potion
        filter_func = keyword_filters.pop("filter", None)

        public = False
        if any(x["rel"] == "instances_public"
               for x in cls._resource._schema["links"]):
            public = keyword_filters.pop("public", False)

        instances_route = keyword_filters.pop(
            "_instances", "instances" if not public else "instances_public")

        schema = next(link for link in cls._resource._schema["links"]
                      if link["rel"] == instances_route)
        sort_schema = schema["schema"]["properties"]["sort"]["properties"]
        where_schema = schema["schema"]["properties"]["where"]["properties"]

        sort = generate_potion_sort_clause(keyword_filters.pop("sort", None),
                                           sort_schema)
        limit = keyword_filters.pop("limit", None if not public else 1000)
        where = {}

        # we're filtering by fancy objects (like SQLAlchemy's filter)
        if len(filters) > 0:
            if len(filters) == 1 and isinstance(filters[0], dict):
                where = filters[0]
            elif all(isinstance(f, six.string_types) for f in filters):
                # if it's a list of strings, treat it as an multiple "get" request
                where = {
                    "$uri": {
                        "$in": [cls._convert_id_to_uri(f) for f in filters]
                    }
                }
            else:
                # we're doing some more advanced filtering
                raise NotImplementedError(
                    "Advanced filtering hasn't been implemented yet")

        # we're filtering by keyword arguments (like SQLAlchemy's filter_by)
        if len(keyword_filters) > 0:
            for k, v in generate_potion_keyword_where(keyword_filters,
                                                      where_schema,
                                                      cls).items():
                if k in where:
                    raise AttributeError(
                        "Multiple definitions for same field {}".format(k))
                where[k] = v

        # the potion-client method returns an iterator (which lazily fetchs the records
        # using `per_page` instances per request) so for limiting we only want to fetch the first
        # n (and not instantiate all the available which is what would happen if we just sliced)
        cursor = getattr(cls._resource,
                         instances_route)(where=where,
                                          sort=sort,
                                          per_page=DEFAULT_PAGE_SIZE)
        if limit is not None:
            cursor = itertools.islice(cursor, limit)

        # finally, apply local filtering function on objects before returning
        wrapped = [cls(_resource=r) for r in cursor]

        if filter_func:
            if callable(filter_func):
                wrapped = [obj for obj in wrapped if filter_func(obj) is True]
            else:
                raise OneCodexException(
                    "Expected callable for filter, got: {}".format(
                        type(filter_func).__name__))

        return wrapped
예제 #8
0
    def where(cls, *filters, **keyword_filters):
        """Retrieves objects (Samples, Classifications, etc.) from the One Codex server.

        Parameters
        ----------
        filters : objects
            Advanced filters to use (not implemented)
        sort : string | list, optional
            Sort the results by this field (or list of fields). By default in descending order,
            but if any of the fields start with the special character ^, sort in ascending order.
            For example, sort=['size', '^filename'] will sort by size from largest to smallest and
            filename from A-Z for items with the same size.
        limit : integer, optional
            Number of records to return. For smaller searches, this can reduce the number of
            network requests made.
        keyword_filters : strings | objects
            Filter the results by specific keywords (or filter objects, in advanced usage)

        Examples
        --------
        You can filter objects that are returned locally using a lambda function:

            # returns only samples with a filename ending in '.gz'
            my_samples = Samples.where(filter=lambda s: s.filename.endswith('.gz'))

        Returns
        -------
        list
            A list of all objects matching these filters. If no filters are passed, this
            matches all objects.
        """
        check_bind(cls)

        # do this here to avoid passing this on to potion
        filter_func = keyword_filters.pop("filter", None)

        public = False
        if any(x["rel"] == "instances_public" for x in cls._resource._schema["links"]):
            public = keyword_filters.pop("public", False)

        instances_route = keyword_filters.pop(
            "_instances", "instances" if not public else "instances_public"
        )

        schema = next(l for l in cls._resource._schema["links"] if l["rel"] == instances_route)
        sort_schema = schema["schema"]["properties"]["sort"]["properties"]
        where_schema = schema["schema"]["properties"]["where"]["properties"]

        sort = generate_potion_sort_clause(keyword_filters.pop("sort", None), sort_schema)
        limit = keyword_filters.pop("limit", None if not public else 1000)
        where = {}

        # we're filtering by fancy objects (like SQLAlchemy's filter)
        if len(filters) > 0:
            if len(filters) == 1 and isinstance(filters[0], dict):
                where = filters[0]
            elif all(isinstance(f, six.string_types) for f in filters):
                # if it's a list of strings, treat it as an multiple "get" request
                where = {"$uri": {"$in": [cls._convert_id_to_uri(f) for f in filters]}}
            else:
                # we're doing some more advanced filtering
                raise NotImplementedError("Advanced filtering hasn't been implemented yet")

        # we're filtering by keyword arguments (like SQLAlchemy's filter_by)
        if len(keyword_filters) > 0:
            for k, v in generate_potion_keyword_where(keyword_filters, where_schema, cls).items():
                if k in where:
                    raise AttributeError("Multiple definitions for same field {}".format(k))
                where[k] = v

        # the potion-client method returns an iterator (which lazily fetchs the records
        # using `per_page` instances per request) so for limiting we only want to fetch the first
        # n (and not instantiate all the available which is what would happen if we just sliced)
        cursor = getattr(cls._resource, instances_route)(
            where=where, sort=sort, per_page=DEFAULT_PAGE_SIZE
        )
        if limit is not None:
            cursor = itertools.islice(cursor, limit)

        # finally, apply local filtering function on objects before returning
        wrapped = [cls(_resource=r) for r in cursor]

        if filter_func:
            if callable(filter_func):
                wrapped = [obj for obj in wrapped if filter_func(obj) is True]
            else:
                raise OneCodexException(
                    "Expected callable for filter, got: {}".format(type(filter_func).__name__)
                )

        return wrapped
예제 #9
0
    def where(cls, *filters, **keyword_filters):
        """
        Retrieves {classname} from the One Codex server.

        Parameters
        ----------
        filters : objects
            Advanced filters to use (not implemented)
        sort : string | list, optional
            Sort the results by this field (or list of fields). By default in descending order,
            but if any of the fields start with the special character ^, sort in ascending order.
            For example, sort=['size', '^filename'] will sort by size from largest to smallest and
            filename from A-Z for items with the same size.
        limit : integer, optional
            Number of records to return. For smaller searches, this can reduce the number of
            network requests made.
        keyword_filters : strings | objects
            Filter the results by specific keywords (or filter objects, in advanced usage)

        Returns
        -------
        list
            A list of all {classname} matching these filters. If no filters are passed, this
            matches all {classname}.
        """.format(classname=cls.__name__)
        check_bind(cls)

        public = False
        if any(x['rel'] == 'instances_public' for x in cls._resource._schema['links']):
            public = keyword_filters.pop('public', False)

        instances_route = keyword_filters.pop('_instances',
                                              'instances' if not public else 'instances_public')

        schema = next(l for l in cls._resource._schema['links'] if l['rel'] == instances_route)
        sort_schema = schema['schema']['properties']['sort']['properties']
        where_schema = schema['schema']['properties']['where']['properties']

        sort = generate_potion_sort_clause(keyword_filters.pop('sort', None), sort_schema)
        limit = keyword_filters.pop('limit', None if not public else 1000)
        where = {}

        # we're filtering by fancy objects (like SQLAlchemy's filter)
        if len(filters) > 0:
            if len(filters) == 1 and isinstance(filters[0], dict):
                where = filters[0]
            elif all(isinstance(f, six.string_types) for f in filters):
                # if it's a list of strings, treat it as an multiple "get" request
                where = {'$uri': {'$in': [cls._convert_id_to_uri(f) for f in filters]}}
            else:
                # we're doing some more advanced filtering
                raise NotImplementedError('Advanced filtering hasn\'t been implemented yet')

        # we're filtering by keyword arguments (like SQLAlchemy's filter_by)
        if len(keyword_filters) > 0:
            for k, v in generate_potion_keyword_where(keyword_filters, where_schema, cls).items():
                if k in where:
                    raise AttributeError('Multiple definitions for same field {}'.format(k))
                where[k] = v

        # the potion-client method returns an iterator (which lazily fetchs the records
        # using `per_page` instances per request) so for limiting we only want to fetch the first
        # n (and not instantiate all the available which is what would happen if we just sliced)
        cursor = getattr(cls._resource, instances_route)(where=where, sort=sort, per_page=DEFAULT_PAGE_SIZE)
        if limit is not None:
            cursor = itertools.islice(cursor, limit)
        return [cls(_resource=r) for r in cursor]