~gotmax23/sourcehutx

sourcehutx: add get_user_ref() and __all__ for all services v2 APPLIED

Maxwell G: 1
 add get_user_ref() and __all__ for all services

 9 files changed, 329 insertions(+), 22 deletions(-)
#1048913 main.yml success
#1048914 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/44083/mbox | git am -3
Learn more about email & git

[PATCH sourcehutx v2] add get_user_ref() and __all__ for all services Export this patch

---
Differences from v1:
* Regenerate test fixutres

 fixtures/cassettes/test_base_get_user | 178 ++++++++++++++++++++++++++
 src/sourcehut/services/_base.py       |  39 ++++++
 src/sourcehut/services/builds.py      |   3 +
 src/sourcehut/services/git.py         |  14 +-
 src/sourcehut/services/lists.py       |  11 +-
 src/sourcehut/services/meta.py        |  25 ++--
 src/sourcehut/services/pages.py       |   3 +
 src/sourcehut/services/todo.py        |  29 ++++-
 tests/integration/test_base.py        |  49 +++++++
 9 files changed, 329 insertions(+), 22 deletions(-)
 create mode 100644 fixtures/cassettes/test_base_get_user
 create mode 100644 tests/integration/test_base.py

diff --git a/fixtures/cassettes/test_base_get_user b/fixtures/cassettes/test_base_get_user
new file mode 100644
index 0000000..a22dfce
--- /dev/null
+++ b/fixtures/cassettes/test_base_get_user
@@ -0,0 +1,178 @@
interactions:
- request:
    body: '{"query": "\n    query getUserRef($username: String!) {\n        user(username:
      $username) {\n            id\ncanonicalName\nusername\nemail\n\n        }\n    }\n    ",
      "variables": {"username": "gotmax23-test"}}'
    headers:
      accept:
      - '*/*'
      accept-encoding:
      - gzip, deflate
      connection:
      - keep-alive
      content-length:
      - '212'
      content-type:
      - application/json
      host:
      - git.sr.ht
      user-agent:
      - python-httpx/0.24.1
    method: POST
    uri: https://git.sr.ht/query
  response:
    content: '{"data":{"user":{"id":45032,"canonicalName":"~gotmax23-test","username":"gotmax23-test","email":"maxwell+client-test@gtmx.me"}}}'
    headers:
      Access-Control-Allow-Headers:
      - User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
      Access-Control-Allow-Methods:
      - GET, POST, OPTIONS
      Access-Control-Allow-Origin:
      - '*'
      Access-Control-Expose-Headers:
      - Content-Length,Content-Range
      Connection:
      - keep-alive
      Content-Length:
      - '128'
      Content-Type:
      - application/json
      Date:
      - Mon, 28 Aug 2023 15:56:18 GMT
      Server:
      - nginx
    http_version: HTTP/1.1
    status_code: 200
- request:
    body: '{"query": "\n    query getUserRef($username: String!) {\n        user(username:
      $username) {\n            id\ncanonicalName\nusername\nemail\n\n        }\n    }\n    ",
      "variables": {"username": "gotmax23-test"}}'
    headers:
      accept:
      - '*/*'
      accept-encoding:
      - gzip, deflate
      connection:
      - keep-alive
      content-length:
      - '212'
      content-type:
      - application/json
      host:
      - lists.sr.ht
      user-agent:
      - python-httpx/0.24.1
    method: POST
    uri: https://lists.sr.ht/query
  response:
    content: '{"data":{"user":{"id":45032,"canonicalName":"~gotmax23-test","username":"gotmax23-test","email":"maxwell+client-test@gtmx.me"}}}'
    headers:
      Access-Control-Allow-Headers:
      - User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
      Access-Control-Allow-Methods:
      - GET, POST, OPTIONS
      Access-Control-Allow-Origin:
      - '*'
      Access-Control-Expose-Headers:
      - Content-Length,Content-Range
      Connection:
      - keep-alive
      Content-Length:
      - '128'
      Content-Type:
      - application/json
      Date:
      - Mon, 28 Aug 2023 15:56:19 GMT
      Server:
      - nginx
    http_version: HTTP/1.1
    status_code: 200
- request:
    body: '{"query": "\n        query getUserRef($username: String!) {\n            userByName(username:
      $username) {\n                id\ncanonicalName\nusername\nemail\n\n            }\n        }\n        ",
      "variables": {"username": "gotmax23-test"}}'
    headers:
      accept:
      - '*/*'
      accept-encoding:
      - gzip, deflate
      connection:
      - keep-alive
      content-length:
      - '242'
      content-type:
      - application/json
      host:
      - meta.sr.ht
      user-agent:
      - python-httpx/0.24.1
    method: POST
    uri: https://meta.sr.ht/query
  response:
    content: '{"data":{"userByName":{"id":45032,"canonicalName":"~gotmax23-test","username":"gotmax23-test","email":"maxwell+client-test@gtmx.me"}}}'
    headers:
      Access-Control-Allow-Headers:
      - User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
      Access-Control-Allow-Methods:
      - GET, POST, OPTIONS
      Access-Control-Allow-Origin:
      - '*'
      Access-Control-Expose-Headers:
      - Content-Length,Content-Range
      Connection:
      - keep-alive
      Content-Length:
      - '134'
      Content-Type:
      - application/json
      Date:
      - Mon, 28 Aug 2023 15:56:19 GMT
      Server:
      - nginx
    http_version: HTTP/1.1
    status_code: 200
- request:
    body: '{"query": "\n    query getUserRef($username: String!) {\n        user(username:
      $username) {\n            id\ncanonicalName\nusername\nemail\n\n        }\n    }\n    ",
      "variables": {"username": "gotmax23-test"}}'
    headers:
      accept:
      - '*/*'
      accept-encoding:
      - gzip, deflate
      connection:
      - keep-alive
      content-length:
      - '212'
      content-type:
      - application/json
      host:
      - todo.sr.ht
      user-agent:
      - python-httpx/0.24.1
    method: POST
    uri: https://todo.sr.ht/query
  response:
    content: '{"data":{"user":{"id":45032,"canonicalName":"~gotmax23-test","username":"gotmax23-test","email":"maxwell+client-test@gtmx.me"}}}'
    headers:
      Access-Control-Allow-Headers:
      - User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
      Access-Control-Allow-Methods:
      - GET, POST, OPTIONS
      Access-Control-Allow-Origin:
      - '*'
      Access-Control-Expose-Headers:
      - Content-Length,Content-Range
      Connection:
      - keep-alive
      Content-Length:
      - '128'
      Content-Type:
      - application/json
      Date:
      - Mon, 28 Aug 2023 15:56:19 GMT
      Server:
      - nginx
    http_version: HTTP/1.1
    status_code: 200
version: 1
diff --git a/src/sourcehut/services/_base.py b/src/sourcehut/services/_base.py
index c7f2234..6513559 100644
--- a/src/sourcehut/services/_base.py
+++ b/src/sourcehut/services/_base.py
@@ -13,6 +13,7 @@ from typing import Any, Generic, TypeVar, Union

from pydantic import BaseModel, Field

from .._utils import check_found as _cf
from .._utils import get_key as _get_key
from .._utils import infinite_iter
from ..client import SRHT_SERVICE, APIVersion, SrhtClient
@@ -20,6 +21,14 @@ from ..client import SRHT_SERVICE, APIVersion, SrhtClient
_ServiceClientT = TypeVar("_ServiceClientT", bound="_ServiceClient")
_BaseResourceT = TypeVar("_BaseResourceT", bound="_BaseResource")
_ResourceT = TypeVar("_ResourceT", bound="_Resource")
_UserRefT = TypeVar("_UserRefT", bound="_UserRef")

_USER_REF_MEMBERS = """\
id
canonicalName
username
email
"""


class _ServiceClient(metaclass=ABCMeta):
@@ -93,3 +102,33 @@ class _Resource(Generic[_ServiceClientT], _BaseResource[_ServiceClientT]):

    def __int__(self) -> int:
        return self.id


async def _get_user_ref(
    client: _ServiceClient,
    typ: type[_UserRefT],
    username: Union[str, _UserRef, None],
) -> _UserRefT:
    username = await client._u(str(username) if username is not None else None)
    query = (
        """
    query getUserRef($username: String!) {
        user(username: $username) {
            %s
        }
    }
    """
        % _USER_REF_MEMBERS
    )
    json = await client.query(query, {"username": username})
    return typ(**_cf(json["user"]), client=client)  # type: ignore[call-arg]


class _UserRef(BaseModel):
    id: int
    username: str
    email: str
    canonical_name: str = Field(alias="canonicalName")

    def __str__(self) -> str:
        return self.username
diff --git a/src/sourcehut/services/builds.py b/src/sourcehut/services/builds.py
index b1e28c6..df67f67 100644
--- a/src/sourcehut/services/builds.py
+++ b/src/sourcehut/services/builds.py
@@ -232,3 +232,6 @@ class Job(_Resource[BuildsSrhtClient]):
        Get the current state of the job
        """
        return await self._client.get_job(self)


__all__ = ("BuildsSrhtClient", "JOB_STATUS", "Job")
diff --git a/src/sourcehut/services/git.py b/src/sourcehut/services/git.py
index 978390f..f4de5f3 100644
--- a/src/sourcehut/services/git.py
+++ b/src/sourcehut/services/git.py
@@ -10,14 +10,14 @@ from __future__ import annotations

from collections.abc import AsyncIterator
from datetime import datetime as DT
from typing import IO, TYPE_CHECKING, Optional
from typing import IO, TYPE_CHECKING, Optional, Union

from .._utils import check_found as _cf
from .._utils import filter_ellipsis
from .._utils import get_key as _g
from .._utils import get_locals, v_submitter
from ..client import SRHT_SERVICE, VISIBILITY, _FileUpload
from ._base import _Resource, _ServiceClient
from ._base import _get_user_ref, _Resource, _ServiceClient, _UserRef

if TYPE_CHECKING:
    from _typeshed import StrPath
@@ -218,6 +218,9 @@ class GitSrhtClient(_ServiceClient):
        """
        await self.query(query, {"artifact": int(artifact)})

    async def get_user_ref(self, username: Union[str, _UserRef, None]) -> UserRef:
        return await _get_user_ref(self, UserRef, username)


class Repository(_Resource[GitSrhtClient]):
    owner: str
@@ -271,3 +274,10 @@ class Artifact(_Resource[GitSrhtClient]):

    async def delete(self) -> None:
        return await self._client.delete_artifact(self)


class UserRef(_UserRef, _Resource[GitSrhtClient]):
    ...


__all__ = ("GitSrhtClient", "Repository", "Artifact", "UserRef")
diff --git a/src/sourcehut/services/lists.py b/src/sourcehut/services/lists.py
index 0eb8eb4..5656e9e 100644
--- a/src/sourcehut/services/lists.py
+++ b/src/sourcehut/services/lists.py
@@ -14,7 +14,7 @@ from .._utils import filter_ellipsis
from .._utils import get_key as _g
from .._utils import get_locals, v_comma_separated_list, v_submitter
from ..client import SRHT_SERVICE, VISIBILITY
from ._base import _Resource, _ServiceClient
from ._base import _get_user_ref, _Resource, _ServiceClient, _UserRef

_LIST_REF_MEMBERS = """
id
@@ -156,6 +156,9 @@ class ListsSrhtClient(_ServiceClient):
        data = await self.query(query, {"listid": listid, "inp": inp})
        return MailingList(**_cf(data["updateMailingList"]), client=self)

    async def get_user_ref(self, username: Union[str, _UserRef, None]) -> UserRef:
        return await _get_user_ref(self, UserRef, username)


class MailingListRef(_Resource[ListsSrhtClient]):
    owner: str
@@ -189,4 +192,8 @@ class MailingList(MailingListRef):
    _v_reject_mime = v_comma_separated_list("reject_mime")


__all__ = ("ListsSrhtClient", "MailingListRef", "MailingList")
class UserRef(_UserRef, _Resource[ListsSrhtClient]):
    ...


__all__ = ("ListsSrhtClient", "MailingListRef", "MailingList", "UserRef")
diff --git a/src/sourcehut/services/meta.py b/src/sourcehut/services/meta.py
index 9379ea4..bbaa5f8 100644
--- a/src/sourcehut/services/meta.py
+++ b/src/sourcehut/services/meta.py
@@ -17,7 +17,7 @@ from pydantic import Field
from .._utils import check_found as _cf
from .._utils import get_locals, v_submitter
from ..client import SRHT_SERVICE
from ._base import _Resource, _ServiceClient
from ._base import _Resource, _ServiceClient, _UserRef

_PGPKey_REF_MEMBERS = """
id
@@ -356,24 +356,10 @@ class SSHKey(SSHKeyRef):
    comment: Optional[str]


class UserRef(_Resource[MetaSrhtClient]):
    username: str
    email: str
    canonical_name: str = Field(alias="canonicalName")

class UserRef(_UserRef, _Resource[MetaSrhtClient]):
    async def get(self) -> User:
        return await self._client.get_user(self)

    def __str__(self) -> str:
        return self.username


class AuditLogEntry(_Resource[MetaSrhtClient]):
    created: DT
    ip_address: str = Field(alias="ipAddress")
    event_type: str = Field(alias="eventType")
    details: Optional[str]


class User(UserRef):
    created: DT
@@ -383,6 +369,13 @@ class User(UserRef):
    bio: Optional[str]


class AuditLogEntry(_Resource[MetaSrhtClient]):
    created: DT
    ip_address: str = Field(alias="ipAddress")
    event_type: str = Field(alias="eventType")
    details: Optional[str]


class Invoice(_Resource[MetaSrhtClient]):
    created: DT
    cents: int
diff --git a/src/sourcehut/services/pages.py b/src/sourcehut/services/pages.py
index 22eddef..06f8695 100644
--- a/src/sourcehut/services/pages.py
+++ b/src/sourcehut/services/pages.py
@@ -178,3 +178,6 @@ class Site(SiteRef):

    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/todo.py b/src/sourcehut/services/todo.py
index e26df92..b766726 100644
--- a/src/sourcehut/services/todo.py
+++ b/src/sourcehut/services/todo.py
@@ -11,7 +11,7 @@ from __future__ import annotations
from collections.abc import AsyncIterator, Iterator
from datetime import datetime as DT
from enum import Enum
from typing import TYPE_CHECKING, Optional, overload
from typing import TYPE_CHECKING, Optional, Union, overload

from pydantic import BaseModel, Field
from pydantic.color import Color
@@ -21,7 +21,7 @@ from .._utils import filter_ellipsis
from .._utils import get_key as _g
from .._utils import get_locals, v_client, v_submitter
from ..client import SRHT_SERVICE, VISIBILITY
from ._base import _BaseResource, _Resource, _ServiceClient
from ._base import _BaseResource, _get_user_ref, _Resource, _ServiceClient, _UserRef

if TYPE_CHECKING:
    pass
@@ -672,6 +672,9 @@ class TodoSrhtClient(_ServiceClient):
        )
        return Label(**_cf(tracker["label"]), tracker=tracker_ref)

    async def get_user_ref(self, username: Union[str, _UserRef, None]) -> UserRef:
        return await _get_user_ref(self, UserRef, username)


class TrackerRef(_Resource[TodoSrhtClient]):
    name: str
@@ -912,3 +915,25 @@ class TrackerACLRef(_Resource[TodoSrhtClient]):

class TrackerACL(TrackerACLRef, TrackerACLBase):
    created: DT


class UserRef(_UserRef, _Resource[TodoSrhtClient]):
    ...


__all__ = (
    "TodoSrhtClient",
    "TrackerRef",
    "Tracker",
    "TICKET_STATUS",
    "TICKET_RESOLUTION",
    "TicketRef",
    "Ticket",
    "Comment",
    "Label",
    "TrackerSubscription",
    "TrackerDefaultACL",
    "TrackerACLRef",
    "TrackerACL",
    "UserRef",
)
diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py
new file mode 100644
index 0000000..c65c470
--- /dev/null
+++ b/tests/integration/test_base.py
@@ -0,0 +1,49 @@
# Copyright (C) 2023 Maxwell G <maxwell@gtmx.me>
# SPDX-License-Identifier: MIT

from __future__ import annotations

import dataclasses

import pytest

from sourcehut.client import SrhtClient
from sourcehut.services import _base, git, lists, meta, todo

from .. import vcr


@dataclasses.dataclass()
class Service:
    client_type: type[_base._ServiceClient]
    user_ref_type: type[_base._UserRef]


SERVICES: tuple[Service, ...] = (
    Service(git.GitSrhtClient, git.UserRef),
    Service(lists.ListsSrhtClient, lists.UserRef),
    Service(meta.MetaSrhtClient, meta.UserRef),
    Service(todo.TodoSrhtClient, todo.UserRef),
)


@pytest.mark.asyncio
@vcr.use_cassette
async def test_base_get_user(authed_client: SrhtClient):
    async with authed_client:
        for service in SERVICES:
            client = service.client_type(authed_client)

            user_ref = await client.get_user_ref(  # type: ignore[attr-defined]
                "gotmax23-test"
            )
            expected_ref = service.user_ref_type(  # type: ignore[call-arg]
                client=client,
                username="gotmax23-test",
                email="maxwell+client-test@gtmx.me",
                canonicalName="~gotmax23-test",
                id=user_ref.id,
            )
            assert user_ref.dict(include={"client"}) == expected_ref.dict(
                include={"client"}
            )
-- 
2.41.0
Applied.
sourcehutx/patches: SUCCESS in 6m16s

[add get_user_ref() and __all__ for all services][0] v2 from [Maxwell G][1]

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

✓ #1048913 SUCCESS sourcehutx/patches/main.yml      https://builds.sr.ht/~gotmax23/job/1048913
✓ #1048914 SUCCESS sourcehutx/patches/mockbuild.yml https://builds.sr.ht/~gotmax23/job/1048914