Skip to content

bookshelf_producer.actions#

bookshelf_producer.actions #

Actions that can be performed on the bookshelf

publish(shelf, book, force=False) #

Publish a book to the remote bookshelf

Parameters:

Name Type Description Default
book LocalBook

Book to upload

required
force bool

If True, overwrite any existing data

False

Raises:

Type Description
UploadError

Unable to upload the book to the remote bookshelf. See error message for more information about how to resolve this issue.

Source code in packages/bookshelf-producer/src/bookshelf_producer/actions.py
def publish(shelf: BookShelf, book: LocalBook, force: bool = False) -> None:
    """
    Publish a book to the remote bookshelf

    Parameters
    ----------
    book : LocalBook
        Book to upload
    force : bool
        If True, overwrite any existing data

    Raises
    ------
    UploadError
        Unable to upload the book to the remote bookshelf. See error message for
        more information about how to resolve this issue.
    """
    if shelf.is_available(book.name, book.version):
        remote_book = shelf.load(book.name, book.version)

        if remote_book.edition >= book.edition:
            msg = (
                "Edition value has not been increased (remote:"
                f" {remote_book.long_version()}, local: {book.long_version()})"
            )
            if not force:
                raise UploadError(msg)
            logger.error(msg)
        logger.warning("Uploading a new edition of an existing book")
    files = book.files()

    # Check if additional files are going to be uploaded
    resources = book.as_datapackage().resources
    resource_fnames = [
        resource.descriptor["filename"] for resource in cast(Iterable[datapackage.Resource], resources)
    ]
    for resource_file in files:
        fname = os.path.basename(resource_file)
        if fname == "datapackage.json":
            continue
        if fname not in resource_fnames:
            raise UploadError(f"Non-resource file {fname} found in book")

    # Upload using boto3 by default for testing
    # Maybe support other upload methods in future

    s3 = boto3.client("s3")
    bucket = get_env_var("BUCKET", add_prefix=True, default=DEFAULT_S3_BUCKET)
    prefix = get_env_var("BUCKET_PREFIX", add_prefix=True, default=DATA_FORMAT_VERSION)

    logger.info(f"Beginning to upload {book.name}@{book.version}")
    for resource_file in files:
        key = "/".join(
            (
                prefix,
                book.name,
                book.long_version(),
                os.path.basename(resource_file),
            )
        )
        _upload_file(s3, bucket, key, resource_file)

    # Update the metadata with the latest version information
    # Note that this doesn't have any guardrails and is susceptible to race conditions
    # Shouldn't be a problem for testing, but shouldn't be used in production
    meta_fname = _update_volume_meta(book, shelf.remote_bookshelf)
    key = "/".join((prefix, book.name, os.path.basename(meta_fname)))
    _upload_file(s3, bucket, key, meta_fname)

    logger.info(f"Book {book.name}@{book.version} ed.{book.edition} uploaded successfully")