Merge branch 'dev' into 'main'

Dev

See merge request trustcafe/trustcafe-api-wrapper!10
This commit is contained in:
Simon Little 2026-04-13 21:48:19 +00:00
commit 89fc8706cb
10 changed files with 194 additions and 36 deletions

2
.gitignore vendored
View file

@ -26,4 +26,6 @@ dist/*
# Test Artifacts
token_data.json
token_data_alpha.json
token_dataproduction.json
test_output.json

View file

@ -25,6 +25,11 @@ potentially misleading.
1. Make more jobs
2. Make more wrappers
3. Write more documentation
4. Wrappers should also accept actual keys for when you know them
5. Add utility to manage the token easily to encourage reuse over
new tokens every time
Trust
Follow

View file

@ -24,6 +24,30 @@ API = APIClient(
debug=False,
)
```
## Handle token (easy way)
```python
API.handle_token()
```
## Handling the token yourself
```python
def getMyToken():
# get the token from your personal store
# initially this will be None
return token_data
def saveMyToken():
# save your token to your personal store
return True
# Use the old token or None if we haven't logged in ever from here
API.set_token(getMyToken())
# Get a new one if we don't have one or if the existing one is expired
if not API.is_token_valid():
saveMyToken(API.sign_in())
```
## Use a wrapper
Wrappers make it as simple as possible to perform an action.

View file

@ -1,6 +1,6 @@
[project]
name = "trustcafeapiwrapper"
version = "0.1.0.10"
version = "0.1.0.11"
description = "Wraps the Trust Cafe API"
readme = "README.md"
requires-python = ">=3.11"

View file

@ -1,7 +1,6 @@
import requests
import os,requests
import simplejson as json
from urllib.parse import urlencode
from pydantic import (
PrivateAttr, BaseModel, SecretStr, HttpUrl
)
@ -153,6 +152,28 @@ class APIClient(BaseModel):
current_time = int(time.time())
return self._access_token and current_time < self._access_token_timeout
def handle_token(self, token_data_path=None):
"""
Handle access token retrieval and caching.
"""
if token_data_path is None:
token_data_path = f"token_data_{self.environment}.json"
if os.path.exists(token_data_path):
with open(token_data_path, "r") as f:
token_data = json.load(f)
self.set_token(token_data)
else:
if self.debug:
print(f"No token data file found at '{token_data_path}'. A new token will be obtained.")
raise FileNotFoundError(f"Token data file '{token_data_path}' not found. Please ensure the file exists or handle token retrieval appropriately.")
# Get a new one if we don't have one or if the existing one is expired
if not self.is_token_valid():
tokendata = self.sign_in()
with open(token_data_path, "w") as f:
json.dump(tokendata, f, indent=2)
def run_job(self, job_function, *args, **kwargs):
"""
Utility method to run a job function with the API client as the first argument.

View file

@ -1,13 +1,16 @@
from trustcafeapiwrapper.utils.get_parent_pksk_from_path import get_parent_pksk_from_path
from trustcafeapiwrapper.utils.get_post_pksk import get_post_pksk
def create_comment(comment_text, post_slug, parent_path, blur_label=None, version=3):
def create_comment(comment_text, post_slug=None, parent_path=None, post_key=None, blur_label=None, version=3):
"""
Creates a new comment.
Args:
comment_text (str): The text content of the comment.
parent_path (str): The parent path for the comment, in the format 'userprofile/slug' or 'subwiki/slug'.
post_slug (str, optional): The slug of the post to which the comment belongs.
parent_path (str, optional): The parent path for the comment, in the format 'userprofile/slug' or 'subwiki/slug'.
post_key (dict, optional): A dictionary containing the primary key (pk) and sort key (sk) of the post.
blur_label (str, optional): An optional label for blurring the comment content.
version (int, optional): The version of the comment structure to use, default is 3.
@ -15,9 +18,20 @@ def create_comment(comment_text, post_slug, parent_path, blur_label=None, versio
dict: A dictionary containing the job name and payload for creating the comment
that will be processed by the API client wrapper function.
"""
parent_pksk = get_parent_pksk_from_path(parent_path)
post_pksk = get_post_pksk(parent_pksk, post_slug)
if post_key is not None:
post_pksk = {
"pk": post_key.get('pk'),
"sk": post_key.get('sk')
}
elif parent_path is not None and post_slug is not None:
parent_pksk = get_parent_pksk_from_path(parent_path)
post_pksk = get_post_pksk(parent_pksk, post_slug)
else:
raise ValueError("Either post_key or both parent_path and post_slug must be provided.")
if comment_text is None or comment_text.strip() == "":
raise ValueError("Comment text cannot be empty.")
return {
"job_function": "comment.create",
"payload": {
@ -26,7 +40,7 @@ def create_comment(comment_text, post_slug, parent_path, blur_label=None, versio
"parent": {
"pk": post_pksk['pk'],
"sk": post_pksk['sk'],
"slug": parent_path.split('/')[-1]
"slug": post_pksk.get('sk', '').replace('post#', ''),
},
"topLevel": {
"pk": post_pksk['pk'],

View file

@ -1,13 +1,15 @@
from trustcafeapiwrapper.utils import get_post_pksk, get_parent_pksk_from_path
def update_post(parent_path, post_path, post_text, blur_label=None, card_url=None, collaborative=False):
def update_post( post_text, post_path=None, parent_path='/', post_key=None, blur_label=None, card_url=None, collaborative=False):
"""
Updates an existing post.
Args:
post_slug (str): The slug of the post to update.
post_text (str): The new text for the post.
post_path (str, optional): The path of the post to update. Defaults to None.
parent_path (str, optional): The parent path for the post. Defaults to '/'.
post_key (dict, optional): A dictionary containing the primary key (pk) and sort key (sk) of the post. Defaults to None.
blur_label (str, optional): The blur label for the post. Defaults to None.
card_url (str, optional): The card URL for the post. Defaults to None.
collaborative (bool, optional): Whether the post is collaborative. Defaults to False.
@ -15,21 +17,38 @@ def update_post(parent_path, post_path, post_text, blur_label=None, card_url=Non
Returns:
dict: The updated post data.
"""
parent_pksk = get_parent_pksk_from_path(parent_path)
post_pksk = get_post_pksk(parent_pksk, post_path)
if post_key is not None:
post_pksk = {
'pk': post_key.get('pk', None),
'sk': post_key.get('sk', None)
}
elif post_path is not None and parent_path is not None:
parent_pksk = get_parent_pksk_from_path(parent_path)
post_pksk = get_post_pksk(parent_pksk, post_path)
else:
raise ValueError("Either post_path and parent_path or post_key must be provided.")
if post_text is None:
raise ValueError("post_text is required.")
payload = {
"key": {
"pk": post_pksk.get('pk', None),
"sk": post_pksk.get('sk', None)
},
"postSlug": post_path.strip('/post/'),
"blurLabel": blur_label,
"cardUrl": card_url,
"postText": post_text,
"collaborative": collaborative,
"postSlug": post_pksk.get('sk', '').replace('post#', ''),
"postText": post_text
}
if collaborative is not None:
payload["collaborative"] = collaborative
if blur_label is not None:
payload["blurLabel"] = blur_label
if card_url is not None:
payload["cardUrl"] = card_url
return {
"job_function": "post.update",

View file

@ -26,16 +26,7 @@ API = APIClient(
# Keep a token cache to avoid unnecessary sign-ins during development.
# (In production, you'd handle this more robustly and securely)
if os.path.exists("token_data.json"):
with open("token_data.json", "r") as f:
token_data = json.load(f)
API.set_token(token_data)
# Get a new one if we don't have one or if the existing one is expired
if not API.is_token_valid():
tokendata = API.sign_in()
with open("token_data.json", "w") as f:
json.dump(tokendata, f, indent=2)
API.handle_token() # This will load the token from file if it exists and is valid, or sign in to get a new one if not.
'''
END IMPORTANT BIT
@ -50,8 +41,8 @@ def save_response(response):
# print("-----------Get a user profile----------------")
# profile = API.run_job('userprofile.get', "simon-little")
# print(profile)
profile = API.run_job('userprofile.get', "simon-little")
print(profile)
# print("-------------- Get a branch -----------------")
# branch = API.run_job('branch.get', "music")
# print(branch)
@ -138,7 +129,7 @@ def save_response(response):
# x += 1
# save_response(API.run_job('notification.listnotifications'))
save_response(API.run_job('notification.markallasread'))
# save_response(API.run_job('notification.markallasread'))
# save_response(API.run_job('feed.cafefeed'))

View file

@ -19,4 +19,23 @@ class TestCreateComment(unittest.TestCase):
self.assertEqual(result["payload"]["commentText"], self.comment_text)
self.assertEqual(result["payload"]["blurLabel"], self.blur_label)
self.assertEqual(result["payload"]["parent"]["pk"], "maintrunk#maintrunk")
self.assertEqual(result["payload"]["parent"]["sk"], "post#1774951384-98fe38df")
self.assertEqual(result["payload"]["parent"]["sk"], "post#1774951384-98fe38df")
def test_create_comment_with_post_key(self):
post_key = {
"pk": "maintrunk#maintrunk",
"sk": "post#1774951384-98fe38df"
}
result = create_comment(
comment_text=self.comment_text,
post_key=post_key,
blur_label=self.blur_label
)
self.assertIsInstance(result, dict)
self.assertIn("job_function", result)
self.assertIn("payload", result)
self.assertEqual(result["job_function"], "comment.create")
self.assertEqual(result["payload"]["commentText"], self.comment_text)
self.assertEqual(result["payload"]["blurLabel"], self.blur_label)
self.assertEqual(result["payload"]["parent"]["pk"], post_key["pk"])
self.assertEqual(result["payload"]["parent"]["sk"], post_key["sk"])

View file

@ -21,10 +21,73 @@ class TestUpdatePost(unittest.TestCase):
self.assertIn("payload", result)
self.assertEqual(result["job_function"], "post.update")
self.assertEqual(result["payload"]["postText"], self.post_text)
self.assertEqual(result["payload"]["blurLabel"], self.blur_label)
self.assertEqual(result["payload"]["cardUrl"], self.card_url)
self.assertEqual(result["payload"]["collaborative"], self.collaborative)
self.assertEqual(result["payload"]["postSlug"], "1235-abcv")
self.assertEqual(result["payload"]["key"]["pk"], "maintrunk#maintrunk")
self.assertEqual(result["payload"]["key"]["sk"], "post#1235-abcv")
self.assertNotIn("slug", result["payload"]["key"])
self.assertNotIn("slug", result["payload"]["key"])
def test_update_post_with_post_key(self):
post_key = {
"pk": "maintrunk#maintrunk",
"sk": "post#1235-abcv"
}
result = update_post(
post_key=post_key,
post_text=self.post_text,
blur_label=self.blur_label,
card_url=self.card_url,
collaborative=self.collaborative,
)
self.assertIsInstance(result, dict)
self.assertIn("job_function", result)
self.assertIn("payload", result)
self.assertEqual(result["job_function"], "post.update")
self.assertEqual(result["payload"]["postText"], self.post_text)
self.assertEqual(result["payload"]["postSlug"], "1235-abcv")
self.assertEqual(result["payload"]["key"]["pk"], post_key["pk"])
self.assertEqual(result["payload"]["key"]["sk"], post_key["sk"])
self.assertNotIn("slug", result["payload"]["key"])
def test_update_collaborative_post(self):
collaborative = True
result = update_post(
parent_path='/',
post_path='/post/1235-abcv',
post_text=self.post_text,
blur_label=self.blur_label,
card_url=self.card_url,
collaborative=collaborative
)
self.assertIsInstance(result, dict)
self.assertIn("job_function", result)
self.assertIn("payload", result)
self.assertEqual(result["job_function"], "post.update")
self.assertEqual(result["payload"]["postText"], self.post_text)
self.assertEqual(result["payload"]["postSlug"], "1235-abcv")
self.assertEqual(result["payload"]["key"]["pk"], "maintrunk#maintrunk")
self.assertEqual(result["payload"]["key"]["sk"], "post#1235-abcv")
self.assertNotIn("slug", result["payload"]["key"])
self.assertTrue(result["payload"]["collaborative"])
def test_update_blur_label_and_card_url(self):
blur_label = "Sensitive Content"
card_url = "https://example.com/card"
result = update_post(
parent_path='/',
post_path='/post/1235-abcv',
post_text=self.post_text,
blur_label=blur_label,
card_url=card_url,
collaborative=self.collaborative
)
self.assertIsInstance(result, dict)
self.assertIn("job_function", result)
self.assertIn("payload", result)
self.assertEqual(result["job_function"], "post.update")
self.assertEqual(result["payload"]["postText"], self.post_text)
self.assertEqual(result["payload"]["postSlug"], "1235-abcv")
self.assertEqual(result["payload"]["key"]["pk"], "maintrunk#maintrunk")
self.assertEqual(result["payload"]["key"]["sk"], "post#1235-abcv")
self.assertNotIn("slug", result["payload"]["key"])
self.assertEqual(result["payload"]["blurLabel"], blur_label)
self.assertEqual(result["payload"]["cardUrl"], card_url)