Skip to content

DorsalFile

dorsal.file.dorsal_file.DorsalFile

DorsalFile(
    hash_string,
    private=None,
    client=None,
    _file_record=None,
)

Bases: _DorsalFile

Represents a file whose metadata is fetched from the DorsalHub platform.

This constructor performs a network request to the DorsalHub API to download the file's record. The operation may take some time depending on network conditions and API responsiveness.

This class provides an object-oriented interface to a file's record on DorsalHub, identified by its hash string.

Attributes:

Name Type Description
hash str

The primary SHA-256 hash of the file content.

name str

The base name of the file.

size int

The file size in bytes.

media_type str

The detected media type of the file.

tags list[FileTag]

A list of tags associated with the file.

annotations object

A container for detailed metadata records.

Example
from dorsal import DorsalFile

# This fetches the record for the given hash from DorsalHub.
file_hash = "known_file_hash_from_dorsalhub"
dorsal_file = DorsalFile(file_hash)

print(f"File: {dorsal_file.name}")

Parameters:

Name Type Description Default
hash_string str

The hash of the file to fetch. Can be prefixed with the algorithm (e.g., "blake3:...") but defaults to SHA-256.

required
private bool

If True, searches for the file in your private records. If False, searches public records. Defaults to False.

None
client DorsalClient

An existing DorsalClient instance to use for the API call. If not provided, a shared instance will be used. Defaults to None.

None

Raises:

Type Description
DorsalClientError

If the API call fails.

NotFoundError

If no file record with the specified hash is found.

Initializes a DorsalFile instance by fetching its metadata from DorsalHub.

If _file_record is provided, it initializes from that data directly.

Parameters:

Name Type Description Default
hash_string str

The hash string (e.g., "sha256:value" or just "value" for SHA-256) of the file to fetch.

required
private (optional)

By default (None) will attempt to download the private file record, and fall back on the public, before raising an error if neither are found.

required
client DorsalClient | None

An optional DorsalClient instance for API communication. If None, a globally shared DorsalClient instance will be used.

None

Raises:

Type Description
DorsalClientError

If an error occurs during instantiation.

TypeError

If hash_string is not a string.

Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def __init__(
    self,
    hash_string: str,
    private: bool | None = None,
    client: DorsalClient | None = None,
    _file_record: "FileRecordDateTime | None" = None,
):
    """
    Initializes a DorsalFile instance by fetching its metadata from DorsalHub.

    If `_file_record` is provided, it initializes from that data directly.

    Args:
        hash_string: The hash string (e.g., "sha256:value" or just "value" for SHA-256)
                     of the file to fetch.
        private (optional) : By default (None) will attempt to download the private file record, and fall
                             back on the public, before raising an error if neither are found.
        client: An optional DorsalClient instance for API communication.
                If None, a globally shared DorsalClient instance will be used.

    Raises:
        DorsalClientError: If an error occurs during instantiation.
        TypeError: If hash_string is not a string.
    """
    if not isinstance(hash_string, str):
        raise TypeError(f"hash_string must be a string, got {type(hash_string).__name__}")

    self._source: Literal["dorsalhub"] = "dorsalhub"
    self._hash_string: str = hash_string
    self._client: DorsalClient = client if client is not None else get_shared_dorsal_client()
    self._private: bool | None = private
    self._is_deleted: bool = False
    self.annotation_stubs: dict[str, list[FileAnnotationStub]] = {}

    file_record_model: "FileRecordDateTime"
    if _file_record:
        file_record_model = _file_record
    else:
        file_record_model = self._download()

    self.date_modified = file_record_model.date_modified.astimezone(tz=datetime.UTC)
    self.date_created = file_record_model.date_created.astimezone(tz=datetime.UTC)

    super().__init__(file_record=file_record_model)
    logger.debug("DorsalFile for hash '%s' initialized successfully.", self.hash)

add_private_tag

add_private_tag(*, name, value, api_key=None)

Adds a single private tag to the remote file record on DorsalHub.

This method makes a direct API call. On success, the local object is refreshed to reflect the new state on the server.

Parameters:

Name Type Description Default
name str

The name of the tag.

required
value Any

The value for the tag (str, bool, int, etc.).

required
api_key str | None

An optional API key for this specific request.

None

Returns:

Name Type Description
DorsalFile DorsalFile

The instance of the class, allowing for method chaining.

Raises:

Type Description
ValueError

If the tag data fails Pydantic validation.

TaggingError

If the server is unable to apply the tag.

DorsalClientError

For underlying client, network, or API errors.

Example
from dorsal import DorsalFile

dorsal_file = DorsalFile(hash_string="YOUR_FILE_HASH_HERE")
dorsal_file.add_private_tag(name="internal_id", value=12345)
Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def add_private_tag(
    self,
    *,
    name: str,
    value: Any,
    api_key: str | None = None,
) -> DorsalFile:
    """Adds a single private tag to the remote file record on DorsalHub.

    This method makes a direct API call. On success, the local object is
    refreshed to reflect the new state on the server.

    Args:
        name (str): The name of the tag.
        value (Any): The value for the tag (str, bool, int, etc.).
        api_key (str | None): An optional API key for this specific request.

    Returns:
        DorsalFile: The instance of the class, allowing for method chaining.

    Raises:
        ValueError: If the tag data fails Pydantic validation.
        TaggingError: If the server is unable to apply the tag.
        DorsalClientError: For underlying client, network, or API errors.

    Example:
        ```python
        from dorsal import DorsalFile

        dorsal_file = DorsalFile(hash_string="YOUR_FILE_HASH_HERE")
        dorsal_file.add_private_tag(name="internal_id", value=12345)
        ```
    """
    from dorsal.file.validators.file_record import NewFileTag

    try:
        new_tag = NewFileTag(
            name=name,
            value=value,
            private=True,
        )
    except PydanticValidationError as err:
        logger.exception("Failed to create NewFileTag(name='%s') from user input.", name)
        raise ValueError(f"Invalid tag data for name='{name}'.") from err

    self._add_tags_remote(tags=[new_tag], api_key=api_key)
    logger.info(
        "Private tag (%s=%s) added successfully to file remote file record. Object refreshed to reflect changes.",
        name,
        value,
    )
    return self

add_public_tag

add_public_tag(*, name, value, api_key=None)

Adds a single public tag to the remote file record on DorsalHub.

This method makes a direct API call. On success, the local object is refreshed to reflect the new state on the server.

Parameters:

Name Type Description Default
name str

Name of the tag (typically 3-64 alphanumeric characters and underscores).

required
value Any

Value of the tag (str, bool, datetime, int, or float).

required
auto_validate

If True, immediately validates the tag against the DorsalHub API (requires internet connection). Defaults to False (lazy validation).

required
api_key str | None

Optional API key to use if auto_validate is True.

None

Returns:

Name Type Description
DorsalFile DorsalFile

The instance of the class, allowing for method chaining.

Raises:

Type Description
ValueError

If the tag data fails Pydantic validation.

TaggingError

If the server is unable to apply the tag.

DorsalClientError

For underlying client, network, or API errors.

Example
from dorsal import DorsalFile

dorsal_file = DorsalFile(hash_string="YOUR_FILE_HASH_HERE")
dorsal_file.add_public_tag(name="release_candidate", value=True)
Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def add_public_tag(
    self,
    *,
    name: str,
    value: Any,
    api_key: str | None = None,
) -> DorsalFile:
    """Adds a single public tag to the remote file record on DorsalHub.

    This method makes a direct API call. On success, the local object is
    refreshed to reflect the new state on the server.

    Args:
        name: Name of the tag (typically 3-64 alphanumeric characters and
              underscores).
        value: Value of the tag (str, bool, datetime, int, or float).
        auto_validate: If True, immediately validates the tag against the
                       DorsalHub API (requires internet connection).
                       Defaults to False (lazy validation).
        api_key: Optional API key to use if auto_validate is True.

    Returns:
        DorsalFile: The instance of the class, allowing for method chaining.

    Raises:
        ValueError: If the tag data fails Pydantic validation.
        TaggingError: If the server is unable to apply the tag.
        DorsalClientError: For underlying client, network, or API errors.

    Example:
        ```python
        from dorsal import DorsalFile

        dorsal_file = DorsalFile(hash_string="YOUR_FILE_HASH_HERE")
        dorsal_file.add_public_tag(name="release_candidate", value=True)
        ```
    """
    from dorsal.file.validators.file_record import NewFileTag

    try:
        new_tag = NewFileTag(
            name=name,
            value=value,
            private=False,
        )
    except PydanticValidationError as err:
        logger.exception("Failed to create NewFileTag(name='%s') from user input.", name)
        raise ValueError(f"Invalid tag data for name='{name}'.") from err

    self._add_tags_remote(tags=[new_tag], api_key=api_key)
    logger.info(
        "Public tag (%s=%s) added successfully to remote file record. Object refreshed to reflect changes.",
        name,
        value,
    )
    return self

add_tags

add_tags(*, public=None, private=None, api_key=None)

Adds multiple tags to the remote file record in one call.

Parameters:

Name Type Description Default
public dict[str, Any] | None

A dictionary of public tags to add {name: value}.

None
private dict[str, Any] | None

A dictionary of private tags to add {name: value}.

None
api_key str | None

An optional API key for this specific request.

None

Returns:

Name Type Description
DorsalFile DorsalFile

The instance of the class, refreshed with the new tags.

Raises:

Type Description
ValueError

If tag names or values are invalid.

DorsalClientError

If the API call fails.

Example
file.add_tags(
    public={"status": "processed", "project": "alpha"},
    private={"reviewer": "me", "priority": 1}
)
Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def add_tags(
    self,
    *,
    public: dict[str, Any] | None = None,
    private: dict[str, Any] | None = None,
    api_key: str | None = None,
) -> DorsalFile:
    """
    Adds multiple tags to the remote file record in one call.

    Args:
        public: A dictionary of public tags to add {name: value}.
        private: A dictionary of private tags to add {name: value}.
        api_key: An optional API key for this specific request.

    Returns:
        DorsalFile: The instance of the class, refreshed with the new tags.

    Raises:
        ValueError: If tag names or values are invalid.
        DorsalClientError: If the API call fails.

    Example:
        ```python
        file.add_tags(
            public={"status": "processed", "project": "alpha"},
            private={"reviewer": "me", "priority": 1}
        )
        ```
    """
    from dorsal.file.validators.file_record import NewFileTag

    tags_to_add: list[NewFileTag] = []

    try:
        if public:
            for name, value in public.items():
                tags_to_add.append(NewFileTag(name=name, value=value, private=False))

        if private:
            for name, value in private.items():
                tags_to_add.append(NewFileTag(name=name, value=value, private=True))

    except PydanticValidationError as err:
        logger.exception("Failed to create tag objects from bulk input.")
        raise ValueError(f"Invalid tag data provided in bulk update: {err}") from err

    if not tags_to_add:
        logger.warning("add_tags called with no public or private tags provided.")
        return self

    self._add_tags_remote(tags=tags_to_add, api_key=api_key)

    logger.info("Bulk added %d tags to remote file record. Object refreshed.", len(tags_to_add))
    return self

delete

delete(
    *,
    record=None,
    tags=None,
    annotations=None,
    api_key=None
)

Deletes this file's record and/or associated data from DorsalHub with granular control.

This method is context-aware. If no scope options are provided, it derives a default behavior from how the object was initialized: - If initialized with private=True, it defaults to deleting only private data (record="private", tags="private", etc.). - If initialized with private=False, it defaults to deleting only public data. - If initialized with private=None (agnostic), it defaults to a "full clean" (record="all", etc.).

You can override this default behavior by providing explicit scope arguments.

Parameters:

Name Type Description Default
record Scope

Specifies which record(s) to delete. If None, uses the context-aware default.

None
tags Scope

Specifies which tags to delete. If None, uses the context-aware default.

None
annotations Scope

Specifies which annotations to delete. If None, uses the context-aware default.

None
api_key str | None

An optional API key for this specific request.

None

Returns:

Name Type Description
FileDeleteResponse 'FileDeleteResponse'

A detailed report of the deletion operation.

Raises:

Type Description
DorsalClientError

If the remote deletion fails due to an API, network, or authentication error.

DorsalError

If this method is called on an already deleted object.

Example
# Assumes 'file' is an initialized DorsalFile(..., private=True)

# Intuitive default: deletes only the private record and metadata
file.delete()

# Powerful override: performs a "full clean" from the private file object
file.delete(record="all", tags="all", annotations="all")
Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def delete(
    self,
    *,
    record: DeletionScope | None = None,
    tags: DeletionScope | None = None,
    annotations: DeletionScope | None = None,
    api_key: str | None = None,
) -> "FileDeleteResponse":
    """
    Deletes this file's record and/or associated data from DorsalHub
    with granular control.

    This method is context-aware. If no scope options are provided, it
    derives a default behavior from how the object was initialized:
    - If initialized with `private=True`, it defaults to deleting only
      private data (`record="private"`, `tags="private"`, etc.).
    - If initialized with `private=False`, it defaults to deleting only
      public data.
    - If initialized with `private=None` (agnostic), it defaults to a
      "full clean" (`record="all"`, etc.).

    You can override this default behavior by providing explicit scope
    arguments.

    Args:
        record (Scope, optional): Specifies which record(s) to delete.
            If None, uses the context-aware default.
        tags (Scope, optional): Specifies which tags to delete.
            If None, uses the context-aware default.
        annotations (Scope, optional): Specifies which annotations to delete.
            If None, uses the context-aware default.
        api_key (str | None): An optional API key for this specific request.

    Returns:
        FileDeleteResponse: A detailed report of the deletion operation.

    Raises:
        DorsalClientError: If the remote deletion fails due to an API,
            network, or authentication error.
        DorsalError: If this method is called on an already deleted object.

    Example:
        ```python
        # Assumes 'file' is an initialized DorsalFile(..., private=True)

        # Intuitive default: deletes only the private record and metadata
        file.delete()

        # Powerful override: performs a "full clean" from the private file object
        file.delete(record="all", tags="all", annotations="all")
        ```
    """
    if not self.hash:
        raise ValueError("Cannot delete a file record with no hash.")

    if self._is_deleted:
        raise DorsalError(f"Cannot delete file {self.hash}: This object has already been deleted.")

    default_scope: DeletionScope
    if self._private is True:
        default_scope = "private"
    elif self._private is False:
        default_scope = "public"
    else:
        default_scope = "all"

    final_record_scope = record if record is not None else default_scope
    final_tags_scope = tags if tags is not None else default_scope
    final_annotations_scope = annotations if annotations is not None else default_scope

    logger.debug(
        "Attempting to remotely delete file (hash: %s) with effective options: record=%s, tags=%s, annotations=%s",
        self.hash,
        final_record_scope,
        final_tags_scope,
        final_annotations_scope,
    )

    try:
        client = self._client or get_shared_dorsal_client(api_key=api_key)
        response = client.delete_file(
            file_hash=self.hash,
            record=final_record_scope,
            tags=final_tags_scope,
            annotations=final_annotations_scope,
            api_key=api_key,
        )
    except DorsalClientError as err:
        logger.exception(
            "An error occurred during the file delete API call for hash %s.",
            self.hash,
        )
        raise err

    self._is_deleted = True
    logger.info(
        "File record for hash %s was successfully deleted from DorsalHub. This local object is now stale.",
        self.hash,
    )

    return response

delete_tag

delete_tag(*, tag_id, api_key=None)

Deletes a specific tag from the remote file record using its unique ID.

This method makes a direct API call to delete the tag. On success, the local object is automatically refreshed with the latest data from the server.

Parameters:

Name Type Description Default
tag_id str

The unique 24-character ID of the tag to delete.

required
api_key str | None

An optional API key for this specific request.

None

Returns:

Name Type Description
DorsalFile DorsalFile

The instance of the class, allowing for method chaining.

Raises:

Type Description
ValueError

If the provided tag_id is not a valid format.

DorsalClientError

For underlying client, network, or API errors, including NotFoundError if the tag does not exist or the user does not have permission to delete it.

Example
from dorsal import DorsalFile

# Assume dorsal_file is an initialized DorsalFile object with tags
dorsal_file = DorsalFile(hash_string="YOUR_FILE_HASH_HERE")

if dorsal_file.tags:
    # Get the ID of the first tag on the file
    id_to_delete = dorsal_file.tags[0].id

    print(f"Attempting to delete tag with ID: {id_to_delete}")
    dorsal_file.delete_tag(tag_id=id_to_delete)
    print("Tag deleted successfully.")
else:
    print("File has no tags to delete.")
Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def delete_tag(self, *, tag_id: str, api_key: str | None = None) -> DorsalFile:
    """Deletes a specific tag from the remote file record using its unique ID.

    This method makes a direct API call to delete the tag. On success, the
    local object is automatically refreshed with the latest data from the server.

    Args:
        tag_id (str): The unique 24-character ID of the tag to delete.
        api_key (str | None): An optional API key for this specific request.

    Returns:
        DorsalFile: The instance of the class, allowing for method chaining.

    Raises:
        ValueError: If the provided tag_id is not a valid format.
        DorsalClientError: For underlying client, network, or API errors,
            including NotFoundError if the tag does not exist or the user
            does not have permission to delete it.

    Example:
        ```python
        from dorsal import DorsalFile

        # Assume dorsal_file is an initialized DorsalFile object with tags
        dorsal_file = DorsalFile(hash_string="YOUR_FILE_HASH_HERE")

        if dorsal_file.tags:
            # Get the ID of the first tag on the file
            id_to_delete = dorsal_file.tags[0].id

            print(f"Attempting to delete tag with ID: {id_to_delete}")
            dorsal_file.delete_tag(tag_id=id_to_delete)
            print("Tag deleted successfully.")
        else:
            print("File has no tags to delete.")
        ```
    """
    if not self.hash:
        raise ValueError("Cannot add delete tags from a file with no hash.")

    if not isinstance(tag_id, str) or not re.match(r"^[0-9a-f]{24}$", tag_id):
        raise ValueError(f"Invalid tag_id format: '{tag_id}'. Must be a 24-character lowercase hex string.")

    logger.debug(
        "Attempting to remotely delete tag (id: %s) from file (hash: %s)",
        tag_id,
        self.hash,
    )

    try:
        client = self._client or get_shared_dorsal_client(api_key=api_key)
        client.delete_tag(file_hash=self.hash, tag_id=tag_id, api_key=api_key)
    except DorsalClientError as err:
        logger.exception(
            "An error occurred during the delete_tag API call for tag id %s.",
            tag_id,
        )
        raise err

    logger.debug(
        "Remote tag id '%s' deleted successfully for file %s. Refreshing local state.",
        tag_id,
        self.hash,
    )
    self.refresh()

    return self

from_record classmethod

from_record(record, client=None)

Alternative constructor to create a DorsalFile instance from an already-fetched FileRecordDateTime object.

This avoids making an additional API call to download the record.

Parameters:

Name Type Description Default
record 'FileRecordDateTime'

The FileRecordDateTime Pydantic model instance.

required
client DorsalClient | None

An optional DorsalClient instance to attach to the object.

None

Returns:

Type Description
'DorsalFile'

An initialized DorsalFile instance.

Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
@classmethod
def from_record(cls, record: "FileRecordDateTime", client: DorsalClient | None = None) -> "DorsalFile":
    """
    Alternative constructor to create a DorsalFile instance from an
    already-fetched FileRecordDateTime object.

    This avoids making an additional API call to download the record.

    Args:
        record: The FileRecordDateTime Pydantic model instance.
        client: An optional DorsalClient instance to attach to the object.

    Returns:
        An initialized DorsalFile instance.
    """
    return cls(hash_string=record.hash, client=client, _file_record=record)

get_annotations

get_annotations(schema_id, source_id=None)

Retrieves a list of annotations (or stubs) from the remote record, by schema_id.

Parameters:

Name Type Description Default
schema_id str

The unique identifier of the dataset/schema (e.g. 'open/classification').

required
source_id str | None

Optional. Filter annotations by their source ID.

None

Returns:

Type Description
Sequence[FileAnnotationStub | PDFValidationModel | MediaInfoValidationModel | EbookValidationModel | OfficeDocumentValidationModel]

A list of FileAnnotationStub objects (for custom schemas) or Core Models.

Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def get_annotations(
    self, schema_id: str, source_id: str | None = None
) -> Sequence[
    FileAnnotationStub
    | PDFValidationModel
    | MediaInfoValidationModel
    | EbookValidationModel
    | OfficeDocumentValidationModel
]:
    """
    Retrieves a list of annotations (or stubs) from the remote record, by schema_id.

    Args:
        schema_id: The unique identifier of the dataset/schema (e.g. 'open/classification').
        source_id: Optional. Filter annotations by their source ID.

    Returns:
        A list of FileAnnotationStub objects (for custom schemas) or Core Models.
    """
    if schema_id == "file/pdf":
        return [self.pdf] if self.pdf else []
    elif schema_id == "file/mediainfo":
        return [self.mediainfo] if self.mediainfo else []
    elif schema_id == "file/ebook":
        return [self.ebook] if self.ebook else []
    elif schema_id == "file/office":
        return [self.office] if self.office else []

    stubs = self.annotation_stubs.get(schema_id, [])

    if source_id is not None:
        return [stub for stub in stubs if stub.source.id == source_id]

    return stubs

refresh

refresh()

Reloads the file record's data from DorsalHub.

This method re-fetches the record from the API, updating the object's attributes with the latest data from the server.

Raises:

Type Description
DorsalClientError

If the API call fails.

NotFoundError

If the file record can no longer be found on the server.

Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def refresh(self) -> None:
    """Reloads the file record's data from DorsalHub.

    This method re-fetches the record from the API, updating the object's
    attributes with the latest data from the server.

    Raises:
        DorsalClientError: If the API call fails.
        NotFoundError: If the file record can no longer be found on the server.
    """
    logger.debug("Refreshing DorsalFile for hash: %s", self._hash_string)

    new_file_record = self._download()

    super().__init__(file_record=new_file_record)
    logger.debug("DorsalFile for hash '%s' refreshed successfully.", self.hash)

set_validation_hash

set_validation_hash(validation_hash)

Sets the BLAKE3 validation hash, potentially upgrading the model.

This method validates the format of the provided BLAKE3 hash string. If valid, it updates the instance's validation_hash and the underlying Pydantic model (self.model). The model is explicitly re-instantiated to ensure all Pydantic validations run.

If the DorsalFile instance's model (initially FileRecordDateTime) has its annotations field populated, setting this validation_hash will upgrade self.model to FileRecordStrict. Otherwise, self.model will remain FileRecordDateTime but with its validation_hash field now set.

This method is specific to DorsalFile and allows users to provide the BLAKE3 hash to "validate" possession of a file, as the server does not disclose this hash for FileRecordDateTime downloads.

Parameters:

Name Type Description Default
validation_hash str

The candidate string for the BLAKE3 validation hash.

required

Raises:

Type Description
TypeError

If blake3_hash_input is not a string.

ValueError

If blake3_hash_input is not a valid BLAKE3 hash format, or if updating the model causes a Pydantic validation error (e.g., hash collision with primary SHA256).

RuntimeError

For unexpected errors during the process.

Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def set_validation_hash(self, validation_hash: str) -> None:
    """Sets the BLAKE3 validation hash, potentially upgrading the model.

    This method validates the format of the provided BLAKE3 hash string.
    If valid, it updates the instance's `validation_hash` and the
    underlying Pydantic model (`self.model`). The model is explicitly
    re-instantiated to ensure all Pydantic validations run.

    If the `DorsalFile` instance's model (initially `FileRecordDateTime`)
    has its `annotations` field populated, setting this `validation_hash`
    will upgrade `self.model` to `FileRecordStrict`. Otherwise, `self.model`
    will remain `FileRecordDateTime` but with its `validation_hash` field now set.

    This method is specific to `DorsalFile` and allows users to provide
    the BLAKE3 hash to "validate" possession of a file, as the server
    does not disclose this hash for `FileRecordDateTime` downloads.

    Args:
        validation_hash: The candidate string for the BLAKE3 validation hash.

    Raises:
        TypeError: If `blake3_hash_input` is not a string.
        ValueError: If `blake3_hash_input` is not a valid BLAKE3 hash format,
                    or if updating the model causes a Pydantic validation
                    error (e.g., hash collision with primary SHA256).
        RuntimeError: For unexpected errors during the process.
    """
    from dorsal.file.validators.file_record import FileRecordDateTime, FileRecordStrict

    if not isinstance(validation_hash, str):
        raise TypeError("Input 'blake3_hash_input' must be a string.")

    logger.debug(
        "DorsalFile: Attempting to set BLAKE3 hash: '%s' for file (current primary hash: %s)",
        validation_hash,
        self.hash,
    )

    normalized_blake3 = hash_string_validator.get_valid_hash(
        candidate_string=validation_hash, hash_function="BLAKE3"
    )

    if normalized_blake3 is None:
        logger.warning(
            "DorsalFile: Invalid BLAKE3 hash format provided: '%s' for file (primary hash: %s)",
            validation_hash,
            self.hash,
        )
        raise ValueError(f"The provided string '{validation_hash}' is not a valid BLAKE3 hash format.")

    current_model_data = self.model.model_dump()
    current_model_data["validation_hash"] = normalized_blake3

    target_model_class: type[FileRecordDateTime] | type[FileRecordStrict]
    if self.model.annotations is not None:
        logger.debug(
            "DorsalFile: Annotations found. Attempting to upgrade model to FileRecordStrict for file (primary hash: %s).",
            self.hash,
        )
        target_model_class = FileRecordStrict
        current_model_data["source"] = "dorsalhub"
    else:
        logger.debug(
            "DorsalFile: Annotations field is None. Model will be FileRecordDateTime with validation_hash for file (primary hash: %s).",
            self.hash,
        )
        target_model_class = FileRecordDateTime

    try:
        updated_model = target_model_class(**current_model_data)
        self.model = updated_model

        self.validation_hash = self.model.validation_hash

        logger.debug(
            "DorsalFile: Successfully set validation_hash to '%s' on model type '%s' for file (primary hash: %s)",
            self.validation_hash,
            self.model.__class__.__name__,
            self.hash,
        )
    except PydanticValidationError as err:
        logger.debug(
            "DorsalFile: Pydantic model validation failed during %s instantiation. Errors: %s",
            target_model_class.__name__,
            err.errors(),
        )
        logger.exception(
            "DorsalFile: Failed to set validation_hash '%s' for file (primary hash: %s) "
            "due to Pydantic model validation error when using target class '%s'.",
            normalized_blake3,
            self.hash,
            target_model_class.__name__,
        )
        raise ValueError(
            f"Failed to apply BLAKE3 hash '{normalized_blake3}'. "
            f"Model validation failed for {target_model_class.__name__}. "
            "Check logs for details."
        ) from err
    except Exception as err:
        logger.exception(
            "DorsalFile: Unexpected error setting validation_hash '%s' for file (primary hash: %s) "
            "with target class '%s'.",
            normalized_blake3,
            self.hash,
            target_model_class.__name__,
        )
        raise RuntimeError(f"Unexpected error setting validation_hash: {err!s}") from err

dorsal.file.dorsal_file.FileAnnotationStub

FileAnnotationStub(stub, parent_file)

Interactive AnnotationStub.

Download the full annotation with download method.

Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def __init__(self, stub: AnnotationStub, parent_file: "DorsalFile"):
    self._parent = parent_file
    self._stub = stub

    self.id: uuid.UUID = self._stub.id
    self.parent_hash = self._stub.hash
    self.source: AnnotationSource = self._stub.source
    self.user_id: int = self._stub.user_id
    self.date_modified: datetime.datetime = self._stub.date_modified
    self.url: str = self._make_url()

download

download()

Fetches full annotation record from DorsalHub.

Returns:

Name Type Description
FileAnnotationResponse FileAnnotationResponse

A Pydantic model of the full annotation record.

Raises:

Type Description
DorsalError

If the DorsalFile parent is not fully instantiated (no hash).

DorsalClientError

If the API call fails for any reason.

Source code in venv/lib/python3.13/site-packages/dorsal/file/dorsal_file.py
def download(self) -> FileAnnotationResponse:
    """
    Fetches full annotation record from DorsalHub.

    Returns:
        FileAnnotationResponse: A Pydantic model of the full annotation record.

    Raises:
        DorsalError: If the `DorsalFile` parent is not fully instantiated (no hash).
        DorsalClientError: If the API call fails for any reason.

    """
    client = self._parent._client or get_shared_dorsal_client()
    if self._parent.hash is None:
        raise DorsalError(f"Invalid `DorsalFile` instance: {str(self._parent)}")
    return client.get_file_annotation(file_hash=self._parent.hash, annotation_id=str(self.id))