spond.club
Client for the Spond Club finance API.
Spond Club is the paid administration tier sold to clubs/teams alongside the
free consumer app. It exposes a separate API (api.spond.com/club/v1/) for
finance-flavoured data such as transactions/payments. Use the SpondClub
class for this API and spond.spond.Spond for everything else.
1"""Client for the Spond Club finance API. 2 3Spond Club is the paid administration tier sold to clubs/teams alongside the 4free consumer app. It exposes a separate API (`api.spond.com/club/v1/`) for 5finance-flavoured data such as transactions/payments. Use the `SpondClub` 6class for this API and `spond.spond.Spond` for everything else. 7""" 8 9from __future__ import annotations 10 11from typing import TYPE_CHECKING, ClassVar 12 13from .base import _SpondBase 14 15if TYPE_CHECKING: 16 from . import JSONDict 17 18 19class SpondClub(_SpondBase): 20 """Async client for the Spond Club finance API. 21 22 Authentication is shared with the consumer API — the same email/password 23 credentials work, but the user must belong to at least one Spond Club 24 organisation and the `club_id` passed to each method must be one they 25 have access to. The `club_id` here is distinct from the consumer-API 26 `groupId`. 27 28 Example 29 ------- 30 ```python 31 import asyncio 32 from spond import club 33 34 async def main(): 35 sc = club.SpondClub(username="me@example.invalid", password="secret") 36 txs = await sc.get_transactions(club_id="ABCD1234...", max_items=50) 37 for t in txs: 38 print(t["paidAt"], t["paymentName"], t["paidByName"]) 39 await sc.clientsession.close() 40 41 asyncio.run(main()) 42 ``` 43 """ 44 45 _API_BASE_URL: ClassVar = "https://api.spond.com/club/v1/" 46 47 def __init__(self, username: str, password: str) -> None: 48 """Construct a Spond Club client. 49 50 Parameters 51 ---------- 52 username : str 53 Spond account email. Same credentials as the consumer API; the 54 account must have access to at least one Spond Club organisation 55 for the API calls to return data. 56 password : str 57 Spond account password. 58 """ 59 super().__init__(username, password, self._API_BASE_URL) 60 self.transactions: list[JSONDict] | None = None 61 62 @_SpondBase.require_authentication 63 async def get_transactions( 64 self, club_id: str, skip: int | None = None, max_items: int = 100 65 ) -> list[JSONDict]: 66 """Retrieve transactions/payments for a Spond Club. 67 68 Spond's transactions endpoint returns at most 25 records per request, 69 so this method paginates internally (via recursion on `skip`) until 70 either `max_items` is reached or the server returns an empty page. 71 72 **Caching caveat**: results accumulate on `self.transactions` and 73 the cache is **not** keyed by `club_id` — calling this method again 74 with a different `club_id` on the same instance will append that 75 club's transactions to the same list, mixing the two. If you query 76 multiple clubs from one client, reset the cache between calls 77 (`sc.transactions = None`) or use a fresh `SpondClub` instance per 78 club. 79 80 Each transaction dict typically includes at least `id`, `paidAt`, 81 `paymentName`, and `paidByName`. See `examples/transactions.py` for 82 a usage example. 83 84 Parameters 85 ---------- 86 club_id : str 87 Identifier for the club. Note that this is **different** from the 88 `groupId` used elsewhere in the Spond API — find it in the URL 89 of the Spond Club web UI. 90 skip : int, optional 91 Pagination cursor (number of records to skip). Normally left as 92 `None`; the method increments it itself on recursive calls. Only 93 override if you know what you're doing. 94 max_items : int, optional 95 Stop fetching once at least this many transactions are 96 accumulated. Defaults to 100. The final list may be slightly 97 longer than `max_items` since the server returns pages of 25. 98 99 Returns 100 ------- 101 list[JSONDict] 102 All transactions accumulated so far (across recursive page 103 fetches). Empty list if the club has no transactions. 104 """ 105 if self.transactions is None: 106 self.transactions = [] 107 108 url = f"{self.api_url}transactions" 109 params = None if skip is None else {"skip": skip} 110 headers = {**self.auth_headers, "X-Spond-Clubid": club_id} 111 112 async with self.clientsession.get(url, headers=headers, params=params) as r: 113 if r.status == 200: 114 t = await r.json() 115 if len(t) == 0: 116 return self.transactions 117 118 self.transactions.extend(t) 119 if len(self.transactions) < max_items: 120 return await self.get_transactions( 121 club_id=club_id, 122 skip=len(t) if skip is None else skip + len(t), 123 max_items=max_items, 124 ) 125 126 return self.transactions
20class SpondClub(_SpondBase): 21 """Async client for the Spond Club finance API. 22 23 Authentication is shared with the consumer API — the same email/password 24 credentials work, but the user must belong to at least one Spond Club 25 organisation and the `club_id` passed to each method must be one they 26 have access to. The `club_id` here is distinct from the consumer-API 27 `groupId`. 28 29 Example 30 ------- 31 ```python 32 import asyncio 33 from spond import club 34 35 async def main(): 36 sc = club.SpondClub(username="me@example.invalid", password="secret") 37 txs = await sc.get_transactions(club_id="ABCD1234...", max_items=50) 38 for t in txs: 39 print(t["paidAt"], t["paymentName"], t["paidByName"]) 40 await sc.clientsession.close() 41 42 asyncio.run(main()) 43 ``` 44 """ 45 46 _API_BASE_URL: ClassVar = "https://api.spond.com/club/v1/" 47 48 def __init__(self, username: str, password: str) -> None: 49 """Construct a Spond Club client. 50 51 Parameters 52 ---------- 53 username : str 54 Spond account email. Same credentials as the consumer API; the 55 account must have access to at least one Spond Club organisation 56 for the API calls to return data. 57 password : str 58 Spond account password. 59 """ 60 super().__init__(username, password, self._API_BASE_URL) 61 self.transactions: list[JSONDict] | None = None 62 63 @_SpondBase.require_authentication 64 async def get_transactions( 65 self, club_id: str, skip: int | None = None, max_items: int = 100 66 ) -> list[JSONDict]: 67 """Retrieve transactions/payments for a Spond Club. 68 69 Spond's transactions endpoint returns at most 25 records per request, 70 so this method paginates internally (via recursion on `skip`) until 71 either `max_items` is reached or the server returns an empty page. 72 73 **Caching caveat**: results accumulate on `self.transactions` and 74 the cache is **not** keyed by `club_id` — calling this method again 75 with a different `club_id` on the same instance will append that 76 club's transactions to the same list, mixing the two. If you query 77 multiple clubs from one client, reset the cache between calls 78 (`sc.transactions = None`) or use a fresh `SpondClub` instance per 79 club. 80 81 Each transaction dict typically includes at least `id`, `paidAt`, 82 `paymentName`, and `paidByName`. See `examples/transactions.py` for 83 a usage example. 84 85 Parameters 86 ---------- 87 club_id : str 88 Identifier for the club. Note that this is **different** from the 89 `groupId` used elsewhere in the Spond API — find it in the URL 90 of the Spond Club web UI. 91 skip : int, optional 92 Pagination cursor (number of records to skip). Normally left as 93 `None`; the method increments it itself on recursive calls. Only 94 override if you know what you're doing. 95 max_items : int, optional 96 Stop fetching once at least this many transactions are 97 accumulated. Defaults to 100. The final list may be slightly 98 longer than `max_items` since the server returns pages of 25. 99 100 Returns 101 ------- 102 list[JSONDict] 103 All transactions accumulated so far (across recursive page 104 fetches). Empty list if the club has no transactions. 105 """ 106 if self.transactions is None: 107 self.transactions = [] 108 109 url = f"{self.api_url}transactions" 110 params = None if skip is None else {"skip": skip} 111 headers = {**self.auth_headers, "X-Spond-Clubid": club_id} 112 113 async with self.clientsession.get(url, headers=headers, params=params) as r: 114 if r.status == 200: 115 t = await r.json() 116 if len(t) == 0: 117 return self.transactions 118 119 self.transactions.extend(t) 120 if len(self.transactions) < max_items: 121 return await self.get_transactions( 122 club_id=club_id, 123 skip=len(t) if skip is None else skip + len(t), 124 max_items=max_items, 125 ) 126 127 return self.transactions
Async client for the Spond Club finance API.
Authentication is shared with the consumer API — the same email/password
credentials work, but the user must belong to at least one Spond Club
organisation and the club_id passed to each method must be one they
have access to. The club_id here is distinct from the consumer-API
groupId.
Example
import asyncio
from spond import club
async def main():
sc = club.SpondClub(username="me@example.invalid", password="secret")
txs = await sc.get_transactions(club_id="ABCD1234...", max_items=50)
for t in txs:
print(t["paidAt"], t["paymentName"], t["paidByName"])
await sc.clientsession.close()
asyncio.run(main())
48 def __init__(self, username: str, password: str) -> None: 49 """Construct a Spond Club client. 50 51 Parameters 52 ---------- 53 username : str 54 Spond account email. Same credentials as the consumer API; the 55 account must have access to at least one Spond Club organisation 56 for the API calls to return data. 57 password : str 58 Spond account password. 59 """ 60 super().__init__(username, password, self._API_BASE_URL) 61 self.transactions: list[JSONDict] | None = None
Construct a Spond Club client.
Parameters
- username (str): Spond account email. Same credentials as the consumer API; the account must have access to at least one Spond Club organisation for the API calls to return data.
- password (str): Spond account password.
63 @_SpondBase.require_authentication 64 async def get_transactions( 65 self, club_id: str, skip: int | None = None, max_items: int = 100 66 ) -> list[JSONDict]: 67 """Retrieve transactions/payments for a Spond Club. 68 69 Spond's transactions endpoint returns at most 25 records per request, 70 so this method paginates internally (via recursion on `skip`) until 71 either `max_items` is reached or the server returns an empty page. 72 73 **Caching caveat**: results accumulate on `self.transactions` and 74 the cache is **not** keyed by `club_id` — calling this method again 75 with a different `club_id` on the same instance will append that 76 club's transactions to the same list, mixing the two. If you query 77 multiple clubs from one client, reset the cache between calls 78 (`sc.transactions = None`) or use a fresh `SpondClub` instance per 79 club. 80 81 Each transaction dict typically includes at least `id`, `paidAt`, 82 `paymentName`, and `paidByName`. See `examples/transactions.py` for 83 a usage example. 84 85 Parameters 86 ---------- 87 club_id : str 88 Identifier for the club. Note that this is **different** from the 89 `groupId` used elsewhere in the Spond API — find it in the URL 90 of the Spond Club web UI. 91 skip : int, optional 92 Pagination cursor (number of records to skip). Normally left as 93 `None`; the method increments it itself on recursive calls. Only 94 override if you know what you're doing. 95 max_items : int, optional 96 Stop fetching once at least this many transactions are 97 accumulated. Defaults to 100. The final list may be slightly 98 longer than `max_items` since the server returns pages of 25. 99 100 Returns 101 ------- 102 list[JSONDict] 103 All transactions accumulated so far (across recursive page 104 fetches). Empty list if the club has no transactions. 105 """ 106 if self.transactions is None: 107 self.transactions = [] 108 109 url = f"{self.api_url}transactions" 110 params = None if skip is None else {"skip": skip} 111 headers = {**self.auth_headers, "X-Spond-Clubid": club_id} 112 113 async with self.clientsession.get(url, headers=headers, params=params) as r: 114 if r.status == 200: 115 t = await r.json() 116 if len(t) == 0: 117 return self.transactions 118 119 self.transactions.extend(t) 120 if len(self.transactions) < max_items: 121 return await self.get_transactions( 122 club_id=club_id, 123 skip=len(t) if skip is None else skip + len(t), 124 max_items=max_items, 125 ) 126 127 return self.transactions
Retrieve transactions/payments for a Spond Club.
Spond's transactions endpoint returns at most 25 records per request,
so this method paginates internally (via recursion on skip) until
either max_items is reached or the server returns an empty page.
Caching caveat: results accumulate on self.transactions and
the cache is not keyed by club_id — calling this method again
with a different club_id on the same instance will append that
club's transactions to the same list, mixing the two. If you query
multiple clubs from one client, reset the cache between calls
(sc.transactions = None) or use a fresh SpondClub instance per
club.
Each transaction dict typically includes at least id, paidAt,
paymentName, and paidByName. See examples/transactions.py for
a usage example.
Parameters
- club_id (str):
Identifier for the club. Note that this is different from the
groupIdused elsewhere in the Spond API — find it in the URL of the Spond Club web UI. - skip (int, optional):
Pagination cursor (number of records to skip). Normally left as
None; the method increments it itself on recursive calls. Only override if you know what you're doing. - max_items (int, optional):
Stop fetching once at least this many transactions are
accumulated. Defaults to 100. The final list may be slightly
longer than
max_itemssince the server returns pages of 25.
Returns
- list[JSONDict]: All transactions accumulated so far (across recursive page fetches). Empty list if the club has no transactions.