예제 #1
0
파일: registry.py 프로젝트: zurk/modelforge
def delete_model(args: argparse.Namespace, backend: StorageBackend,
                 log: logging.Logger):
    """
    Delete a model.

    :param args: :class:`argparse.Namespace` with "input", "backend", "args", "meta", \
                        "update_default", "username", "password", "remote_repo", \
                        "template_model", "template_readme" and "log_level".
    :param backend: Backend which is responsible for working with model files.
    :param log: Logger supplied by supply_backend
    :return: None
    """
    try:
        meta = backend.index.remove_model(args.input)
        template_readme = backend.index.load_template(args.template_readme)
        backend.index.update_readme(template_readme)
    except ValueError:
        return 1
    backend.delete_model(meta)
    log.info("Updating the models index...")
    try:
        backend.index.upload("delete", meta)
    except ValueError:  # TODO: replace with PorcelainError
        return 1
    log.info("Successfully deleted.")
예제 #2
0
def publish_model(args: argparse.Namespace, backend: StorageBackend,
                  log: logging.Logger):
    """
    Pushes the model to Google Cloud Storage and updates the index file.

    :param args: :class:`argparse.Namespace` with "model", "backend", "args", "force" and \
                 "update_default".
    :return: None if successful, 1 otherwise.
    """
    path = os.path.abspath(args.model)
    try:
        model = GenericModel(source=path, dummy=True)
    except ValueError as e:
        log.critical('"model" must be a path: %s', e)
        return 1
    except Exception as e:
        log.critical("Failed to load the model: %s: %s" %
                     (type(e).__name__, e))
        return 1
    meta = model.meta
    with backend.lock():
        model_url = backend.upload_model(path, meta, args.force)
        log.info("Uploaded as %s", model_url)
        log.info("Updating the models index...")
        index = backend.fetch_index()
        index["models"].setdefault(meta["model"], {})[meta["uuid"]] = \
            extract_index_meta(meta, model_url)
        if args.update_default:
            index["models"][meta["model"]][Model.DEFAULT_NAME] = meta["uuid"]
        backend.upload_index(index)
예제 #3
0
def initialize_registry(args: argparse.Namespace, backend: StorageBackend, log: logging.Logger):
    """
    Initialize the registry and the index.
    :param args: :class:`argparse.Namespace` with "backend", "args", "force" and "log_level".
    :param backend: Backend which is responsible for working with model files.
    :param log: Logger supplied by supply_backend
    :return: None
    """
    try:
        backend.reset(args.force)
    except ExistingBackendError:
        return 1

    log.info("Resetting the index ...")
    backend.index.reset()
    try:
        backend.index.upload("initialize", {})
    except ValueError:
        return 1
    log.info("Successfully initialized")
예제 #4
0
def initialize_registry(args: argparse.Namespace, backend: StorageBackend,
                        log: logging.Logger):
    """
    Initialize the registry - list and publish will fail otherwise.

    :param args: :class:`argparse.Namespace` with "backend", "args" and "force".
    :return: None
    """

    try:
        backend.fetch_index()
        if not args.force:
            log.warning("Registry is already initialized")
            return
    except FileNotFoundError:
        pass
    # The lock is not needed here, but upload_index() will raise otherwise
    with backend.lock():
        backend.upload_index({"models": {}})
    log.info("Successfully initialized")
예제 #5
0
파일: registry.py 프로젝트: zurk/modelforge
def publish_model(args: argparse.Namespace, backend: StorageBackend,
                  log: logging.Logger):
    """
    Push the model to Google Cloud Storage and updates the index file.

    :param args: :class:`argparse.Namespace` with "model", "backend", "args", "force", "meta" \
                 "update_default", "username", "password", "remote_repo", "template_model", \
                 "template_readme" and "log_level".
    :param backend: Backend which is responsible for working with model files.
    :param log: Logger supplied by supply_backend
    :return: None if successful, 1 otherwise.
    """
    path = os.path.abspath(args.model)
    try:
        model = GenericModel(source=path, dummy=True)
    except ValueError as e:
        log.critical('"model" must be a path: %s', e)
        return 1
    except Exception as e:
        log.critical("Failed to load the model: %s: %s" %
                     (type(e).__name__, e))
        return 1
    base_meta = model.meta
    try:
        model_url = backend.upload_model(path, base_meta, args.force)
    except ModelAlreadyExistsError:
        return 1

    log.info("Uploaded as %s", model_url)
    with open(os.path.join(args.meta), encoding="utf-8") as _in:
        extra_meta = json.load(_in)
    model_type, model_uuid = base_meta["model"], base_meta["uuid"]
    meta = extract_model_meta(base_meta, extra_meta, model_url)
    log.info("Updating the models index...")
    try:
        template_model = backend.index.load_template(args.template_model)
        template_readme = backend.index.load_template(args.template_readme)
    except ValueError:
        return 1
    backend.index.add_model(model_type, model_uuid, meta, template_model,
                            args.update_default)
    backend.index.update_readme(template_readme)
    try:
        backend.index.upload("add", {"model": model_type, "uuid": model_uuid})
    except ValueError:  # TODO: replace with PorcelainError, see related TODO in index.py:181
        return 1
    log.info("Successfully published.")
예제 #6
0
def list_models(args: argparse.Namespace, backend: StorageBackend,
                log: logging.Logger):
    """
    Outputs the list of known models in the registry.

    :param args: :class:`argparse.Namespace` with "backend" and "args".
    :return: None
    """
    index = backend.fetch_index()
    for key, val in index["models"].items():
        print(key)
        default = None
        for mid, meta in val.items():
            if mid == "default":
                default = meta
                break
        for mid, meta in sorted(
            [m for m in val.items() if m[1] != default],
                key=lambda m: parse_datetime(m[1]["created_at"]),
                reverse=True):
            print("  %s %s" % ("*" if default == mid else " ", mid),
                  meta["created_at"])
예제 #7
0
    def load(self,
             source: Union[str, BinaryIO, "Model"] = None,
             cache_dir: str = None,
             backend: StorageBackend = None,
             lazy=False) -> "Model":
        """
        Build a new Model instance.

        :param source: UUID, file system path, file object or an URL; None means auto.
        :param cache_dir: The directory where to store the downloaded model.
        :param backend: Remote storage backend to use if ``source`` is a UUID or a URL.
        :param lazy: Do not really load numpy arrays into memory. Instead, mmap() them. \
                     User is expected to call Model.close() when the tree is no longer needed.
        """
        if isinstance(source, Model):
            if not isinstance(source, type(self)):
                raise TypeError("Incompatible model instance: %s <> %s" %
                                (type(source), type(self)))
            self.__dict__ = source.__dict__
            return self

        if backend is not None and not isinstance(backend, StorageBackend):
            raise TypeError("backend must be an instance of "
                            "modelforge.storage_backend.StorageBackend")
        self._source = str(source)
        generic = self.NAME == self.GENERIC_NAME
        try:
            if source is None or (isinstance(source, str)
                                  and not os.path.isfile(source)):
                if cache_dir is None:
                    if not generic:
                        cache_dir = os.path.join(self.cache_dir(), self.NAME)
                    else:
                        cache_dir = tempfile.mkdtemp(prefix="modelforge-")
                try:
                    uuid.UUID(source)
                    is_uuid = True
                except (TypeError, ValueError):
                    is_uuid = False
                model_id = self.DEFAULT_NAME if not is_uuid else source
                file_name = model_id + self.DEFAULT_FILE_EXT
                file_name = os.path.join(os.path.expanduser(cache_dir),
                                         file_name)
                if os.path.exists(file_name) and (not source or
                                                  not os.path.exists(source)):
                    source = file_name
                elif source is None or is_uuid:
                    if backend is None:
                        raise ValueError(
                            "The backend must be set to load a UUID or the default "
                            "model.")
                    index = backend.index.contents
                    config = index["models"]
                    if not generic:
                        if not is_uuid:
                            model_id = index["meta"][self.NAME][model_id]
                        source = config[self.NAME][model_id]
                    else:
                        if not is_uuid:
                            raise ValueError(
                                "File path, URL or UUID is needed.")
                        for models in config.values():
                            if source in models:
                                source = models[source]
                                break
                        else:
                            raise FileNotFoundError("Model %s not found." %
                                                    source)
                    source = source["url"]
                if re.match(r"\w+://", source):
                    if backend is None:
                        raise ValueError(
                            "The backend must be set to load a URL.")
                    backend.fetch_model(source, file_name)
                    self._source = source
                    source = file_name
            if isinstance(source, str):
                size = os.stat(source).st_size
            else:
                self._source = "<file object>"
                pos = source.tell()
                size = source.seek(0, os.SEEK_END) - pos
                source.seek(pos, os.SEEK_SET)
            self._log.info("Reading %s (%s)...", source,
                           humanize.naturalsize(size))
            model = asdf.open(source, copy_arrays=not lazy, lazy_load=lazy)
            try:
                tree = model.tree
                self._meta = tree["meta"]
                self._initial_version = list(self.version)
                if not generic:
                    meta_name = self._meta["model"]
                    matched = self.NAME == meta_name
                    if not matched:
                        needed = {self.NAME}
                        for child in type(self).__subclasses__():
                            needed.add(child.NAME)
                            matched |= child.NAME == meta_name
                        if not matched:
                            raise ValueError(
                                "The supplied model is of the wrong type: needed "
                                "%s, got %s." % (needed, meta_name))
                self._load_tree(tree)
            finally:
                if not lazy:
                    model.close()
                else:
                    self._asdf = model
        finally:
            if generic and cache_dir is not None:
                shutil.rmtree(cache_dir)
        self._size = size
        return self
예제 #8
0
파일: model.py 프로젝트: afcarl/modelforge
    def load(self,
             source: Union[str, BinaryIO, "Model"] = None,
             cache_dir: str = None,
             backend: StorageBackend = None) -> "Model":
        """
        Initializes a new Model instance.
        :param source: UUID, file system path, file object or an URL; None means auto.
        :param cache_dir: The directory where to store the downloaded model.
        :param backend: Remote storage backend to use if ``source`` is a UUID or a URL.
        """
        if isinstance(source, Model):
            if not isinstance(source, type(self)):
                raise TypeError("Incompatible model instance: %s <> %s" %
                                (type(source), type(self)))
            self.__dict__ = source.__dict__
            return self

        if backend is not None and not isinstance(backend, StorageBackend):
            raise TypeError("backend must be an instance of "
                            "modelforge.storage_backend.StorageBackend")
        self._source = str(source)
        try:
            if source is None or (isinstance(source, str)
                                  and not os.path.isfile(source)):
                if cache_dir is None:
                    if self.NAME is not None:
                        cache_dir = os.path.join(self.cache_dir(), self.NAME)
                    else:
                        cache_dir = tempfile.mkdtemp(prefix="modelforge-")
                try:
                    uuid.UUID(source)
                    is_uuid = True
                except (TypeError, ValueError):
                    is_uuid = False
                model_id = self.DEFAULT_NAME if not is_uuid else source
                file_name = model_id + self.DEFAULT_FILE_EXT
                file_name = os.path.join(os.path.expanduser(cache_dir),
                                         file_name)
                if os.path.exists(file_name) and (not source or
                                                  not os.path.exists(source)):
                    source = file_name
                elif source is None or is_uuid:
                    if backend is None:
                        raise ValueError(
                            "The backend must be set to load a UUID or the default "
                            "model.")
                    index = backend.index.contents
                    config = index["models"]
                    if self.NAME is not None:
                        if not is_uuid:
                            model_id = index["meta"][self.NAME][model_id]
                        source = config[self.NAME][model_id]
                    else:
                        if not is_uuid:
                            raise ValueError(
                                "File path, URL or UUID is needed.")
                        for models in config.values():
                            if source in models:
                                source = models[source]
                                break
                        else:
                            raise FileNotFoundError("Model %s not found." %
                                                    source)
                    source = source["url"]
                if re.match(r"\w+://", source):
                    if backend is None:
                        raise ValueError(
                            "The backend must be set to load a URL.")
                    backend.fetch_model(source, file_name)
                    source = file_name
            self._log.info("Reading %s...", source)
            with asdf.open(source) as model:
                tree = model.tree
                self._meta = tree["meta"]
                if self.NAME is not None:
                    meta_name = self._meta["model"]
                    matched = self.NAME == meta_name
                    if not matched:
                        needed = {self.NAME}
                        for child in type(self).__subclasses__():
                            needed.add(child.NAME)
                            matched |= child.NAME == meta_name
                        if not matched:
                            raise ValueError(
                                "The supplied model is of the wrong type: needed "
                                "%s, got %s." % (needed, meta_name))
                self._load_tree(tree)
        finally:
            if self.NAME is None and cache_dir is not None:
                shutil.rmtree(cache_dir)
        return self