Skip to content

Resources

Each resource class is exposed as a namespace on the client (e.g. client.torrentsTorrentResource). Methods call the underlying Gazelle ajax.php action and return typed models.

pygazelle.resources.torrents.TorrentResource

TorrentResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

get async

get(torrent_id: int) -> Torrent
Source code in src/pygazelle/resources/torrents.py
async def get(self, torrent_id: int) -> Torrent:
    data = await self._transport.request("torrent", id=torrent_id)
    torrent_data = data.get("torrent")
    if torrent_data is None:
        raise GazelleAPIError(status_code=200, message="missing 'torrent' key in response")
    return Torrent.model_validate({**torrent_data, "group": data.get("group")})

get_group async

get_group(group_id: int) -> TorrentGroup
Source code in src/pygazelle/resources/torrents.py
async def get_group(self, group_id: int) -> TorrentGroup:
    data = await self._transport.request("torrentgroup", id=group_id)
    group_data = data.get("group")
    if group_data is None:
        raise GazelleAPIError(status_code=200, message="missing 'group' key in response")
    # Use `or []` not a .get default: the API may send "torrents": null, and
    # passing None would raise an opaque pydantic error instead.
    return TorrentGroup.model_validate({**group_data, "torrents": data.get("torrents") or []})

search async

search(
    query: str, **params: str | int
) -> list[TorrentResult]
Source code in src/pygazelle/resources/torrents.py
async def search(self, query: str, **params: str | int) -> list[TorrentResult]:
    data = await self._transport.request("browse", searchstr=query, **params)
    return self._parse_list(data.get("results"), TorrentResult)

download async

download(torrent_id: int) -> bytes
Source code in src/pygazelle/resources/torrents.py
async def download(self, torrent_id: int) -> bytes:
    return await self._transport.download(torrent_id)

add_tag async

add_tag(
    group_id: int, tags: str | list[str]
) -> TagAddition

Add tag(s) to a torrent group (action=add_tag).

tags may be a single tag, a comma-separated string, or a list of tags (joined into the comma-separated tagname the API expects). Raises ValueError if no tags are given.

Source code in src/pygazelle/resources/torrents.py
async def add_tag(self, group_id: int, tags: str | list[str]) -> TagAddition:
    """Add tag(s) to a torrent group (action=add_tag).

    ``tags`` may be a single tag, a comma-separated string, or a list of
    tags (joined into the comma-separated ``tagname`` the API expects).
    Raises ``ValueError`` if no tags are given.
    """
    if not tags:
        raise ValueError("add_tag requires at least one tag")
    tagname = tags if isinstance(tags, str) else ",".join(tags)
    data = await self._transport.request_write(
        "add_tag", data={"groupid": group_id, "tagname": tagname}
    )
    return TagAddition.model_validate(data)

add_log async

add_log(
    torrent_id: int, logfiles: bytes | list[bytes]
) -> LogAddition

Attach rip log file(s) to a torrent (action=add_log).

The torrent id is a query param; the logs are multipart logfiles[]. Raises ValueError if no log files are given.

Source code in src/pygazelle/resources/torrents.py
async def add_log(self, torrent_id: int, logfiles: bytes | list[bytes]) -> LogAddition:
    """Attach rip log file(s) to a torrent (action=add_log).

    The torrent id is a query param; the logs are multipart ``logfiles[]``.
    Raises ``ValueError`` if no log files are given.
    """
    if not logfiles:
        raise ValueError("add_log requires at least one log file")
    blobs = [logfiles] if isinstance(logfiles, bytes) else list(logfiles)
    files = [("logfiles[]", (f"rip{i}.log", blob)) for i, blob in enumerate(blobs)]
    data = await self._transport.request_write(
        "add_log", params={"id": torrent_id}, files=files
    )
    return LogAddition.model_validate(data)

pygazelle.resources.artists.ArtistResource

ArtistResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

get async

get(artist_id: int) -> Artist
Source code in src/pygazelle/resources/artists.py
async def get(self, artist_id: int) -> Artist:
    data = await self._transport.request("artist", id=artist_id)
    return Artist.model_validate(data)

search async

search(name: str) -> list[ArtistResult]
Source code in src/pygazelle/resources/artists.py
async def search(self, name: str) -> list[ArtistResult]:
    data = await self._transport.request("browse", searchstr=name, artistname=name)
    seen: dict[int, ArtistResult] = {}
    for result in data.get("results", []):
        artist_id = result.get("artistId")
        if artist_id is not None and artist_id not in seen:
            seen[artist_id] = ArtistResult(id=artist_id, name=result.get("artist", ""))
    return list(seen.values())

similar async

similar(
    artist_id: int, limit: int | None = None
) -> list[SimilarArtist]
Source code in src/pygazelle/resources/artists.py
async def similar(self, artist_id: int, limit: int | None = None) -> list[SimilarArtist]:
    params = self._params(id=artist_id, limit=limit)
    # similar_artists returns a bare JSON array (not a {results: ...} object);
    # _parse_list tolerates that and a null response (no similar artists).
    data = await self._transport.request("similar_artists", **params)
    return self._parse_list(data, SimilarArtist)

pygazelle.resources.user.UserResource

UserResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

me async

me() -> User
Source code in src/pygazelle/resources/user.py
async def me(self) -> User:
    data = await self._transport.request("index")
    return User.model_validate(data)

get async

get(user_id: int) -> UserProfile
Source code in src/pygazelle/resources/user.py
async def get(self, user_id: int) -> UserProfile:
    data = await self._transport.request("user", id=user_id)
    # The API omits the id (it's the request param); inject it for the model.
    return UserProfile.model_validate({**data, "id": user_id})

search async

search(
    query: str, **params: str | int
) -> list[UserSearchResult]
Source code in src/pygazelle/resources/user.py
async def search(self, query: str, **params: str | int) -> list[UserSearchResult]:
    data = await self._transport.request("usersearch", search=query, **params)
    return self._parse_list(data.get("results"), UserSearchResult)

torrents async

torrents(
    user_id: int,
    type: UserTorrentType,
    limit: int | None = None,
    offset: int | None = None,
) -> list[UserTorrent]
Source code in src/pygazelle/resources/user.py
async def torrents(
    self,
    user_id: int,
    type: UserTorrentType,
    limit: int | None = None,
    offset: int | None = None,
) -> list[UserTorrent]:
    params = self._params(id=user_id, type=type, limit=limit, offset=offset)
    data = await self._transport.request("user_torrents", **params)
    # The torrents list is keyed under the requested type (e.g. "seeding": [...]).
    return self._parse_list(data.get(type), UserTorrent)

announce_url async

announce_url() -> str

The current user's announce URL on this tracker (passkey + announce host).

Source code in src/pygazelle/resources/user.py
async def announce_url(self) -> str:
    """The current user's announce URL on this tracker (passkey + announce host)."""
    me = await self.me()
    host = self._transport.announce_host
    if not me.passkey or not host:
        raise GazelleError("announce URL unavailable: missing passkey or announce host")
    return f"https://{host}/{me.passkey}/announce"

pygazelle.resources.notifications.NotificationResource

NotificationResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

list async

list(**params: str | int) -> list[Notification]
Source code in src/pygazelle/resources/notifications.py
async def list(self, **params: str | int) -> list[Notification]:
    data = await self._transport.request("notifications", **params)
    return self._parse_list(data.get("results"), Notification)

pygazelle.resources.requests.RequestResource

RequestResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

get async

get(request_id: int) -> Request
Source code in src/pygazelle/resources/requests.py
async def get(self, request_id: int) -> Request:
    data = await self._transport.request("request", id=request_id)
    return Request.model_validate(data)

search async

search(
    query: str, **params: str | int
) -> list[RequestResult]
Source code in src/pygazelle/resources/requests.py
async def search(self, query: str, **params: str | int) -> list[RequestResult]:
    data = await self._transport.request("requests", search=query, **params)
    return [RequestResult.model_validate(r) for r in data.get("results", [])]

fill async

fill(
    request_id: int,
    *,
    torrent_id: int | None = None,
    link: str | None = None,
    user: str | None = None,
) -> RequestFill

Mark a request filled (action=request_fill).

Provide either torrent_id or a link to the filling torrent; user is mod-only. Raises ValueError if neither is given.

Source code in src/pygazelle/resources/requests.py
async def fill(
    self,
    request_id: int,
    *,
    torrent_id: int | None = None,
    link: str | None = None,
    user: str | None = None,
) -> RequestFill:
    """Mark a request filled (action=request_fill).

    Provide either ``torrent_id`` or a ``link`` to the filling torrent;
    ``user`` is mod-only. Raises ``ValueError`` if neither is given.
    """
    if torrent_id is None and link is None:
        raise ValueError("request_fill requires either torrent_id or link")
    body = self._params(requestid=request_id, torrentid=torrent_id, link=link, user=user)
    data = await self._transport.request_write("request_fill", data=body)
    return RequestFill.model_validate(data)

pygazelle.resources.collages.CollageResource

CollageResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

get async

get(collage_id: int) -> Collage
Source code in src/pygazelle/resources/collages.py
async def get(self, collage_id: int) -> Collage:
    data = await self._transport.request("collage", id=collage_id)
    return Collage.model_validate(data)

pygazelle.resources.inbox.InboxResource

InboxResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

list async

list(**params: str | int) -> list[Message]
Source code in src/pygazelle/resources/inbox.py
async def list(self, **params: str | int) -> list[Message]:
    data = await self._transport.request("inbox", type="inbox", **params)
    return [Message.model_validate(m) for m in data.get("messages", [])]

get async

get(conversation_id: int) -> list[Message]
Source code in src/pygazelle/resources/inbox.py
async def get(self, conversation_id: int) -> list[Message]:
    data = await self._transport.request("inbox", type="viewconv", id=conversation_id)
    return [Message.model_validate(m) for m in data.get("messages", [])]

pygazelle.resources.bookmarks.BookmarkResource

BookmarkResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

torrents async

torrents() -> list[BookmarkedTorrentGroup]
Source code in src/pygazelle/resources/bookmarks.py
async def torrents(self) -> list[BookmarkedTorrentGroup]:
    data = await self._transport.request("bookmarks", type="torrents")
    return self._parse_list(data.get("bookmarks"), BookmarkedTorrentGroup)

artists async

artists() -> list[BookmarkedArtist]
Source code in src/pygazelle/resources/bookmarks.py
async def artists(self) -> list[BookmarkedArtist]:
    data = await self._transport.request("bookmarks", type="artists")
    return self._parse_list(data.get("artists"), BookmarkedArtist)

pygazelle.resources.subscriptions.SubscriptionResource

SubscriptionResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

list async

Source code in src/pygazelle/resources/subscriptions.py
async def list(self) -> list[ForumSubscription]:
    data = await self._transport.request("subscriptions")
    return self._parse_list(data.get("threads"), ForumSubscription)

pygazelle.resources.site.SiteResource

SiteResource(transport: SupportsTransport)

Bases: BaseResource

Source code in src/pygazelle/resources/base.py
def __init__(self, transport: SupportsTransport) -> None:
    self._transport: SupportsTransport = transport

top10 async

top10(
    type: Top10Type = "torrents", limit: int | None = None
) -> list[Top10Category]
Source code in src/pygazelle/resources/site.py
async def top10(
    self, type: Top10Type = "torrents", limit: int | None = None
) -> list[Top10Category]:
    params = self._params(type=type, limit=limit)
    # top10 returns a bare JSON array of category objects.
    data = await self._transport.request("top10", **params)
    return self._parse_list(data, Top10Category)

announcements async

announcements() -> Announcements
Source code in src/pygazelle/resources/site.py
async def announcements(self) -> Announcements:
    data = await self._transport.request("announcements")
    return Announcements.model_validate(data)