Maxwell G: 1 add get_user_ref() and __all__ for all services 10 files changed, 367 insertions(+), 22 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~gotmax23/sourcehutx/patches/44082/mbox | git am -3Learn more about email & git
--- fixtures/cassettes/test_base_get_user | 178 ++++++++++++++++++++++++++ fixtures/cassettes/test_get_user | 38 ++++++ 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 +++++++ 10 files changed, 367 insertions(+), 22 deletions(-) create mode 100644 fixtures/cassettes/test_base_get_user create mode 100644 fixtures/cassettes/test_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..35ac487 --- /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: + - '236' + 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 02:42:57 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: + - '236' + 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 02:42:57 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 02:42:57 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: + - '236' + 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 02:42:57 GMT + Server: + - nginx + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/fixtures/cassettes/test_get_user b/fixtures/cassettes/test_get_user new file mode 100644 index 0000000..b641753 --- /dev/null +++ b/fixtures/cassettes/test_get_user @@ -0,0 +1,38 @@ +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: + - '236' + content-type: + - application/json + host: + - builds.sr.ht + user-agent: + - python-httpx/0.24.1 + method: POST + uri: https://builds.sr.ht/query + response: + content: '{"errors":[{"message":"Cannot query field \"user\" on type \"Query\".","locations":[{"line":3,"column":13}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}],"data":null}' + headers: + Connection: + - keep-alive + Content-Length: + - '172' + Content-Type: + - application/json + Date: + - Mon, 28 Aug 2023 01:00:38 GMT + Server: + - nginx + http_version: HTTP/1.1 + status_code: 422 +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
builds.sr.ht <builds@sr.ht>sourcehutx/patches: SUCCESS in 6m26s [add get_user_ref() and __all__ for all services][0] from [Maxwell G][1] [0]: https://lists.sr.ht/~gotmax23/sourcehutx/patches/44082 [1]: mailto:maxwell@gtmx.me ✓ #1048909 SUCCESS sourcehutx/patches/mockbuild.yml https://builds.sr.ht/~gotmax23/job/1048909 ✓ #1048908 SUCCESS sourcehutx/patches/main.yml https://builds.sr.ht/~gotmax23/job/1048908