~gotmax23/sourcehutx

sourcehutx: all: add more docstrings and clean up API v1 APPLIED

Maxwell G: 1
 all: add more docstrings and clean up API

 9 files changed, 273 insertions(+), 8 deletions(-)
#1112359 main.yml success
#1112360 mockbuild.yml success
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~gotmax23/sourcehutx/patches/47615/mbox | git am -3
Learn more about email & git

[PATCH sourcehutx] all: add more docstrings and clean up API Export this patch

---
 src/sourcehut/client.py          | 64 ++++++++++++++++++++++++++++++--
 src/sourcehut/services/_base.py  | 19 ++++++++++
 src/sourcehut/services/builds.py | 18 ++++++++-
 src/sourcehut/services/git.py    | 22 +++++++++++
 src/sourcehut/services/lists.py  | 13 +++++++
 src/sourcehut/services/meta.py   | 38 +++++++++++++++++++
 src/sourcehut/services/pages.py  | 46 +++++++++++++++++++++--
 src/sourcehut/services/paste.py  | 27 ++++++++++++++
 src/sourcehut/services/todo.py   | 34 +++++++++++++++++
 9 files changed, 273 insertions(+), 8 deletions(-)

diff --git a/src/sourcehut/client.py b/src/sourcehut/client.py
index d0c3bc5..ea83720 100644
--- a/src/sourcehut/client.py
+++ b/src/sourcehut/client.py
@@ -27,6 +27,10 @@ _NOT_FOUND = "the requested element is null which the schema does not allow"


class SRHT_SERVICE(str, Enum):
    """
    Sourcehut services
    """

    GIT = "git"
    HG = "hg"
    BUILDS = "builds"
@@ -44,6 +48,10 @@ class SRHT_SERVICE(str, Enum):


class VISIBILITY(str, Enum):
    """
    Visibility options shared across service APIs
    """

    PUBLIC = "PUBLIC"
    UNLISTED = "UNLISTED"
    PRIVATE = "PRIVATE"
@@ -53,6 +61,10 @@ class VISIBILITY(str, Enum):


class APIVersion(NamedTuple):
    """
    Sourcehut API version shared across service APIs
    """

    major: int
    minor: int
    patch: int
@@ -62,7 +74,15 @@ class APIVersion(NamedTuple):


@dataclasses.dataclass(frozen=True)
class _FileUpload:
class FileUpload:
    """
    Represents a GraphQL file upload

    Args:
        content:
            Content to upload as `str`, `bytes`, or a `bytes` stream
    """

    content: _FileContentType
    filename: str | os.PathLike[str] | None

@@ -70,10 +90,13 @@ class _FileUpload:
        return (str(self.filename), self.content) if self.filename else self.content


_FileUpload = FileUpload


def _get_upload_data(
    query: str,
    variables: dict[str, Any],
    uploads: dict[str, _FileUpload | Sequence[_FileUpload]],
    uploads: dict[str, FileUpload | Sequence[FileUpload]],
) -> tuple[
    # data
    dict[str, dict[str, Any]],
@@ -86,7 +109,7 @@ def _get_upload_data(
    data_map: dict[str, list[str]] = {}

    for variable, file in uploads.items():
        if isinstance(file, _FileUpload):
        if isinstance(file, FileUpload):
            files[str(file_index)] = file._get_tuple()
            data_map[str(file_index)] = [f"variables.{variable}"]
            variables[variable] = variables.get(variable, None)
@@ -138,11 +161,18 @@ class SrhtClient:
    def from_config(
        cls: type[_ClientT], config: SrhtConfig, client: httpx.AsyncClient | None = None
    ) -> _ClientT:
        """
        Create a Sourcehut client from a
        [`SrhtConfig`][sourcehut.config.SrhtConfig] object
        """
        if not config.api_token:
            raise ValueError("api_token is not provided in the config")
        return cls(config.baseurl, config.api_token, client, protocol=config.protocol)

    def get_endpoint(self, service: SRHT_SERVICE) -> str:
        """
        Get a endpoint for a particular [`SRHT_SERVICE`][sourcehut.client.SRHT_SERVICE]
        """
        return f"{self.protocol}{service}.{self.baseurl}/query"

    @property
@@ -157,6 +187,27 @@ class SrhtClient:
        *,
        extra_params: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """
        Perform a GraphQL query and return the data payload.

        Args:
            service:
                The [`SRHT_SERVICE`][sourcehut.client.SRHT_SERVICE] to query
            variables:
                GraphQL query variables
            extra_params:
                Extra parameters to pass to `httpx.AsyncClient.post()`

        Returns:
            A dictionary of the query's `data` response payload

        Raises:
            SrhtClientError:
                The query returned a non-2XX return code and/or contained an
                `error` responde payload
            ResourceNotFoundError:
                A resource was not found
        """
        payload = {"query": query, "variables": variables or {}}
        # data = {"operations": json_dumps(payload)}
        extra_params = extra_params or {}
@@ -178,7 +229,7 @@ class SrhtClient:
        service: SRHT_SERVICE,
        query: str,
        variables: dict[str, Any],
        uploads: dict[str, _FileUpload | Sequence[_FileUpload]],
        uploads: dict[str, FileUpload | Sequence[FileUpload]],
        *,
        extra_params: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
@@ -215,6 +266,10 @@ class SrhtClient:
        return json["data"]

    async def whoami(self, service: SRHT_SERVICE) -> str:
        """
        Returns:
            The authenticated username, minus the `~`
        """
        query = """
        query {
          me {
@@ -260,4 +315,5 @@ __all__ = (
    "APIVersion",
    "SrhtClient",
    "DEFAULT_BASEURL",
    "FileUpload",
)
diff --git a/src/sourcehut/services/_base.py b/src/sourcehut/services/_base.py
index 93cf5d5..cdc8e66 100644
--- a/src/sourcehut/services/_base.py
+++ b/src/sourcehut/services/_base.py
@@ -58,6 +58,25 @@ class _ServiceClient(metaclass=ABCMeta):
        *,
        extra_params: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """
        Perform a GraphQL query and return the data payload.

        Args:
            variables:
                GraphQL query variables
            extra_params:
                Extra parameters to pass to `httpx.AsyncClient.post()`

        Returns:
            A dictionary of the query's `data` response payload

        Raises:
            sourcehut.exceptions.SrhtClientError:
                The query returned a non-2XX return code and/or contained an
                `error` responde payload
            sourcehut.exceptions.ResourceNotFoundError:
                A resource was not found
        """
        return await self.client.query(
            self.SERVICE, query, variables, extra_params=extra_params
        )
diff --git a/src/sourcehut/services/builds.py b/src/sourcehut/services/builds.py
index e9aecaa..f5994b2 100644
--- a/src/sourcehut/services/builds.py
+++ b/src/sourcehut/services/builds.py
@@ -201,6 +201,10 @@ class BuildsSrhtClient(_ServiceClient):


class JOB_STATUS(str, Enum):
    """
    String enum of possible job statuses
    """

    PENDING = "PENDING"
    QUEUED = "QUEUED"
    RUNNING = "RUNNING"
@@ -226,6 +230,10 @@ class JOB_STATUS(str, Enum):


class Job(_Resource[BuildsSrhtClient]):
    """
    Model representing a builds.sr.ht job. Do not instantiate directly!
    """

    created: DT
    updated: DT
    status: JOB_STATUS
@@ -259,14 +267,22 @@ class Job(_Resource[BuildsSrhtClient]):


class Secret(_Resource[BuildsSrhtClient]):
    """
    Model representing a builds.sr.ht secret. Do not instantiate directly!
    """

    created: DT
    uuid: str
    name: Optional[str]


class SecretFile(Secret):
    """
    Model representing a builds.sr.ht secret. Do not instantiate directly!
    """

    path: str
    mode: int


__all__ = ("BuildsSrhtClient", "JOB_STATUS", "Job")
__all__ = ("BuildsSrhtClient", "JOB_STATUS", "Job", "Secret", "SecretFile")
diff --git a/src/sourcehut/services/git.py b/src/sourcehut/services/git.py
index f4de5f3..d03c5de 100644
--- a/src/sourcehut/services/git.py
+++ b/src/sourcehut/services/git.py
@@ -189,6 +189,20 @@ class GitSrhtClient(_ServiceClient):
        filename: StrPath,
        content: str | bytes | IO[bytes],
    ) -> Artifact:
        """
        Upload an artifact to a tag

        Args:
            repoid:
                [`Repository`][sourcehut.services.git.Repository] object
                or repository ID
            revspec:
                Reference to which artifact should be uploaded
            filename:
                Name of artifact file
            content:
                The context as a `str`, `bytes`, or a `bytes` IO stream
        """
        query = """
        mutation upload($repoid: Int!, $revspec: String!, $file: Upload!) {
          uploadArtifact(repoId: $repoid, revspec: $revspec, file: $file) {
@@ -223,6 +237,10 @@ class GitSrhtClient(_ServiceClient):


class Repository(_Resource[GitSrhtClient]):
    """
    Git repository model. Do not instantiate directly!
    """

    owner: str
    name: str
    visibility: VISIBILITY
@@ -266,6 +284,10 @@ class Repository(_Resource[GitSrhtClient]):


class Artifact(_Resource[GitSrhtClient]):
    """
    Git artifact model. Do not instantiate directly!
    """

    created: DT
    filename: str
    checksum: str
diff --git a/src/sourcehut/services/lists.py b/src/sourcehut/services/lists.py
index 5656e9e..a2474a5 100644
--- a/src/sourcehut/services/lists.py
+++ b/src/sourcehut/services/lists.py
@@ -45,6 +45,10 @@ else:


class ListsSrhtClient(_ServiceClient):
    """
    Client for lists.sr.ht
    """

    SERVICE = SRHT_SERVICE.LISTS

    async def create_list(
@@ -161,6 +165,11 @@ class ListsSrhtClient(_ServiceClient):


class MailingListRef(_Resource[ListsSrhtClient]):
    """
    Lightweight model representing a reference to a lists.sr.ht mailing list
    with methods to query and modify the list.
    """

    owner: str
    name: str
    _v_owner = v_submitter("owner")
@@ -180,6 +189,10 @@ class MailingListRef(_Resource[ListsSrhtClient]):


class MailingList(MailingListRef):
    """
    Full model representing a lists.sr.ht mailing list.
    """

    created: DT
    updated: DT
    description: Optional[str]
diff --git a/src/sourcehut/services/meta.py b/src/sourcehut/services/meta.py
index bbaa5f8..3382640 100644
--- a/src/sourcehut/services/meta.py
+++ b/src/sourcehut/services/meta.py
@@ -89,6 +89,10 @@ source


class MetaSrhtClient(_ServiceClient):
    """
    Client for meta.sr.ht
    """

    SERVICE = SRHT_SERVICE.META

    async def create_pgp_key(self, key: str) -> PGPKey:
@@ -313,6 +317,11 @@ class MetaSrhtClient(_ServiceClient):


class PGPKeyRef(_Resource[MetaSrhtClient]):
    """
    Lightweight model representing a reference to a user's PGP key with methods
    to query and modify the key.
    """

    user: str
    fingerprint: str
    _v_user = v_submitter("user")
@@ -328,6 +337,10 @@ class PGPKeyRef(_Resource[MetaSrhtClient]):


class PGPKey(PGPKeyRef):
    """
    Full model representing a PGP key.
    """

    created: DT
    last_used: Optional[DT] = Field(alias="lastUsed")
    key: str
@@ -335,6 +348,11 @@ class PGPKey(PGPKeyRef):


class SSHKeyRef(_Resource[MetaSrhtClient]):
    """
    Lightweight model representing a reference to a user's SHH key with methods
    to query and modify the key.
    """

    user: str
    fingerprint: str
    _v_user = v_submitter("user")
@@ -350,6 +368,10 @@ class SSHKeyRef(_Resource[MetaSrhtClient]):


class SSHKey(SSHKeyRef):
    """
    Full model representing an SSH key
    """

    created: DT
    last_used: Optional[DT] = Field(alias="lastUsed")
    key: str
@@ -357,11 +379,19 @@ class SSHKey(SSHKeyRef):


class UserRef(_UserRef, _Resource[MetaSrhtClient]):
    """
    Lightweight model representing a reference to a meta.sr.ht user
    """

    async def get(self) -> User:
        return await self._client.get_user(self)


class User(UserRef):
    """
    Full model of a meta.sr.ht user
    """

    created: DT
    updated: DT
    url: Optional[str]
@@ -370,6 +400,10 @@ class User(UserRef):


class AuditLogEntry(_Resource[MetaSrhtClient]):
    """
    Model of a meta.sr.ht audit log entry
    """

    created: DT
    ip_address: str = Field(alias="ipAddress")
    event_type: str = Field(alias="eventType")
@@ -377,6 +411,10 @@ class AuditLogEntry(_Resource[MetaSrhtClient]):


class Invoice(_Resource[MetaSrhtClient]):
    """
    Model of a meta.sr.ht invoice entry
    """

    created: DT
    cents: int
    valid_thru: DT = Field(alias="validThru")
diff --git a/src/sourcehut/services/pages.py b/src/sourcehut/services/pages.py
index 06f8695..74b7fff 100644
--- a/src/sourcehut/services/pages.py
+++ b/src/sourcehut/services/pages.py
@@ -39,6 +39,10 @@ notFound


class SITE_PROTOCOL(str, Enum):
    """
    String enum of supportred pages.sr.ht site protocols
    """

    HTTPS = "https"
    GEMINI = "gemini"

@@ -48,6 +52,16 @@ class SITE_PROTOCOL(str, Enum):

@dataclass()
class FileConfig:
    """
    Represents file configurations used by the `PagesSrhtClient.publish()` method.

    Attributes:
        glob:
            Glob of files to which this config should apply
        cacheControl:
            Value of the Cache-Control header to be used when serving the file
    """

    glob: str
    cache_control: Optional[str] = None

@@ -81,6 +95,23 @@ class PagesSrhtClient(_ServiceClient):
        not_found: Optional[str] = None,
        file_configs: Optional[List[FileConfig]] = None,
    ) -> Site:
        """
        Publish a pages.sr.ht site

        Args:
            domain:
                (Sub-)domain name
            content:
                `bytes` or `bytes` stream of a gziped tar archive of the site content
            protocol:
                Site protocol
            subdirectory:
                Optionally, publish the site to a subdirectory of `domain`
            not_found:
                Optionally, serve a custom 404 page
            file_configs:
                Optionally, specify configuration for specific files
        """
        inp = get_locals(**locals())
        inp["siteConfig"] = _SiteConfig(
            inp.pop("not_found"), inp.pop("file_configs")
@@ -160,6 +191,11 @@ class PagesSrhtClient(_ServiceClient):


class SiteRef(_Resource[PagesSrhtClient]):
    """
    Lightweight reference to a pages.sr.ht site with methods to query and
    modify the site
    """

    domain: str
    protocol: SITE_PROTOCOL

@@ -169,15 +205,19 @@ class SiteRef(_Resource[PagesSrhtClient]):
            value = SITE_PROTOCOL.HTTPS
        return value

    async def unpublish(self) -> Site:
        return await self._client.unpublish(self.domain, self.protocol)


class Site(SiteRef):
    """
    Full model of a pages.sr.ht site
    """

    created: DT
    updated: DT
    version: str
    not_found: Optional[str] = Field(None, alias="notFound")

    async def unpublish(self) -> Site:
        return await self._client.unpublish(self.domain, self.protocol)


__all__ = ("SITE_PROTOCOL", "FileConfig", "SiteRef", "Site")
diff --git a/src/sourcehut/services/paste.py b/src/sourcehut/services/paste.py
index f8c56bd..9367f6a 100644
--- a/src/sourcehut/services/paste.py
+++ b/src/sourcehut/services/paste.py
@@ -39,11 +39,25 @@ files {


class PasteSrhtClient(_ServiceClient):
    """
    paste.sr.ht client
    """

    SERVICE = SRHT_SERVICE.PASTE

    async def create(
        self, files: Sequence[FileUpload], visibility: VISIBILITY = VISIBILITY.UNLISTED
    ) -> Paste:
        """
        Create a paste

        Args:
            files:
                A list of [`FileUpload`][sourcehut.client.FileUpload] objects
                to attach to the paste
            visibility:
                The paste's visibility
        """
        query = (
            """
        mutation createPaste($files: [Upload!]!, $visibility: Visibility!) {
@@ -132,12 +146,21 @@ class PasteSrhtClient(_ServiceClient):


class File(_BaseResource[PasteSrhtClient]):
    """
    File in a paste.sr.ht paste
    """

    filename: Optional[str]
    hash: str
    contents: str


class PasteRef(_BaseResource[PasteSrhtClient]):
    """
    Lightweight model referencing a paste.sr.ht paste with methods to query and
    modify the paste
    """

    id: str
    user: str
    _v_user = v_submitter("user")
@@ -153,6 +176,10 @@ class PasteRef(_BaseResource[PasteSrhtClient]):


class Paste(PasteRef):
    """
    Full model of a paste.sr.ht paste
    """

    created: DT
    visibility: VISIBILITY
    files: List[File]
diff --git a/src/sourcehut/services/todo.py b/src/sourcehut/services/todo.py
index b766726..6732098 100644
--- a/src/sourcehut/services/todo.py
+++ b/src/sourcehut/services/todo.py
@@ -70,6 +70,10 @@ entity {


class TodoSrhtClient(_ServiceClient):
    """
    todo.sr.ht client
    """

    SERVICE = SRHT_SERVICE.TODO

    async def create_tracker(
@@ -677,6 +681,11 @@ class TodoSrhtClient(_ServiceClient):


class TrackerRef(_Resource[TodoSrhtClient]):
    """
    Lightweight model referencing a todo.sr.ht tracker with methods to query
    and modify the tracker
    """

    name: str
    owner: str

@@ -791,6 +800,10 @@ class TrackerRef(_Resource[TodoSrhtClient]):


class Tracker(TrackerRef):
    """
    Full model of a tracker
    """

    created: DT
    updated: DT
    description: Optional[str]
@@ -798,6 +811,10 @@ class Tracker(TrackerRef):


class TICKET_STATUS(str, Enum):
    """
    String enum of tracker ticket statuses
    """

    REPORTED = "REPORTED"
    CONFIRMED = "CONFIRMED"
    IN_PROGRESS = "IN_PROGRESS"
@@ -806,6 +823,10 @@ class TICKET_STATUS(str, Enum):


class TICKET_RESOLUTION(str, Enum):
    """
    String enum of tracker ticket resolutions
    """

    UNRESOLVED = "UNRESOLVED"
    CLOSED = "CLOSED"
    FIXED = "FIXED"
@@ -818,6 +839,11 @@ class TICKET_RESOLUTION(str, Enum):


class TicketRef(_Resource[TodoSrhtClient]):
    """
    Lightweight model referencing a tracker ticket with methods to query and
    modify the ticket
    """

    tracker: TrackerRef
    _v_client = v_client("tracker")

@@ -829,6 +855,10 @@ class TicketRef(_Resource[TodoSrhtClient]):


class Ticket(TicketRef):
    """
    Full model of a tracker ticket
    """

    created: DT
    updated: DT
    ref: str
@@ -855,6 +885,10 @@ class Comment:


class Label(_Resource[TodoSrhtClient]):
    """
    Model of a tracker label
    """

    created: DT
    tracker: TrackerRef
    name: str
-- 
2.43.0
sourcehutx/patches: SUCCESS in 11m14s

[all: add more docstrings and clean up API][0] from [Maxwell G][1]

[0]: https://lists.sr.ht/~gotmax23/sourcehutx/patches/47615
[1]: mailto:maxwell@gtmx.me

✓ #1112360 SUCCESS sourcehutx/patches/mockbuild.yml https://builds.sr.ht/~gotmax23/job/1112360
✓ #1112359 SUCCESS sourcehutx/patches/main.yml      https://builds.sr.ht/~gotmax23/job/1112359
Applied.