Source code for action0.github.models.content
"""The repository content models (:py:class:`ContentFile`,
:py:class:`DirectoryEntry`, :py:class:`FileCommit`)."""
from __future__ import annotations
import base64
from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from .commit import GitCommit
[docs]
class ContentType(StrEnum):
"""What a repository content entry is."""
FILE = "file"
DIR = "dir"
SYMLINK = "symlink"
SUBMODULE = "submodule"
[docs]
@dataclass
class ContentFile:
"""
A file fetched through the contents API — the object payload of
:py:class:`~action0.github.operations.contents.GetContent` and
:py:class:`~action0.github.operations.contents.GetReadme`.
The bytes arrive base64-encoded in :py:attr:`content`; use
:py:attr:`decoded` (or :py:attr:`text`) instead of touching the raw
transport encoding.
"""
name: str
"""The file name, e.g. ``"README.md"``."""
path: str
"""The path within the repository."""
sha: str
"""The git blob sha (what update/delete operations would need)."""
size: int
"""The file size in bytes."""
type: ContentType
"""What the entry is — requesting a symlink or submodule path
yields an object without content."""
html_url: str | None = None
"""The web URL."""
download_url: str | None = None
"""The direct (CDN) download URL — the fallback for files whose
content GitHub does not inline."""
encoding: str | None = None
"""The transport encoding of :py:attr:`content` — ``"base64"``, or
``"none"`` when GitHub declined to inline the bytes (files between
1 and 100 MB; fetch those via :py:attr:`download_url`)."""
content: str | None = None
"""The base64-encoded bytes (use :py:attr:`decoded`)."""
@property
def decoded(self) -> bytes:
"""
The decoded file bytes.
:raises ValueError: if the payload carries no inlined content
(``encoding: "none"``, or a symlink/submodule entry) —
fetch via :py:attr:`download_url` instead
"""
if self.content is None or self.encoding != "base64":
raise ValueError(
f"{self.path}: no inlined content (encoding={self.encoding!r})"
" — fetch via download_url"
)
return base64.b64decode(self.content)
@property
def text(self) -> str:
"""
The decoded file content as text (UTF-8).
:raises ValueError: if the payload carries no inlined content
:raises UnicodeDecodeError: if the bytes are not valid UTF-8
"""
return self.decoded.decode("utf-8")
[docs]
@classmethod
def from_json(cls, data: Any) -> ContentFile:
"""
Build a content file from one decoded JSON object.
:param data: the decoded JSON object
:return: the content file
"""
return cls(
name=data["name"],
path=data["path"],
sha=data["sha"],
size=data["size"],
type=ContentType(data["type"]),
html_url=data.get("html_url"),
download_url=data.get("download_url"),
encoding=data.get("encoding"),
content=data.get("content"),
)
[docs]
@dataclass
class DirectoryEntry:
"""
One entry of a directory listing — the array items
:py:class:`~action0.github.operations.contents.GetContent` returns
for a directory path. Entries carry no content; fetch a file of
interest with its own ``GetContent(file_path=entry.path)``.
"""
name: str
"""The entry name, e.g. ``"app.py"``."""
path: str
"""The path within the repository."""
sha: str
"""The git object sha."""
size: int
"""The file size in bytes (0 for directories)."""
type: ContentType
"""What the entry is."""
html_url: str | None = None
"""The web URL."""
download_url: str | None = None
"""The direct download URL (``None`` for directories)."""
[docs]
@classmethod
def from_json(cls, data: Any) -> DirectoryEntry:
"""
Build a directory entry from one decoded JSON array item.
:param data: the decoded JSON array item
:return: the entry
"""
return cls(
name=data["name"],
path=data["path"],
sha=data["sha"],
size=data["size"],
type=ContentType(data["type"]),
html_url=data.get("html_url"),
download_url=data.get("download_url"),
)
[docs]
@dataclass
class FileCommit:
"""
The answer of the contents *write* operations
(:py:class:`~action0.github.operations.contents.CreateOrUpdateFile`,
:py:class:`~action0.github.operations.contents.DeleteFile`): the
commit GitHub created, plus the resulting file entry.
"""
commit: GitCommit
"""The commit the write produced."""
content: ContentFile | None = None
"""The written file — its fresh blob :py:attr:`~ContentFile.sha` is
what the *next* update of the same file needs. ``None`` after a
delete."""
[docs]
@classmethod
def from_json(cls, data: Any) -> FileCommit:
"""
Build a file commit from one decoded JSON object.
:param data: the decoded JSON object
:return: the file commit
"""
content = data.get("content")
return cls(
commit=GitCommit.from_json(data["commit"]),
content=ContentFile.from_json(content) if content is not None else None,
)