Maxwell G: 1 all: add more docstrings and clean up API 9 files changed, 273 insertions(+), 8 deletions(-)
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 -3Learn more about email & git
--- 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
builds.sr.ht <builds@sr.ht>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.