From 590fae14072aaabb1cb02a683f7927c124a02f20 Mon Sep 17 00:00:00 2001 From: simonwt Date: Tue, 7 Apr 2026 22:32:13 +0100 Subject: [PATCH 1/7] changing environment variables --- src/trustcafeapiwrapper/apiclient.py | 32 ++++++++++++++++++++++++++-- tests/apiclient.py | 19 +++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/trustcafeapiwrapper/apiclient.py b/src/trustcafeapiwrapper/apiclient.py index 0fe78ef..6e31fcb 100644 --- a/src/trustcafeapiwrapper/apiclient.py +++ b/src/trustcafeapiwrapper/apiclient.py @@ -11,7 +11,17 @@ endpoints = { "auth": "https://oo0wks9pbi.execute-api.us-east-1.amazonaws.com/alpha/", "content": "https://w1yygdhayc.execute-api.us-east-1.amazonaws.com/alpha/", "megaphone": "https://opdhjaktnl.execute-api.us-east-1.amazonaws.com/alpha/", - } + "moderation": "https://sbb1xrqsf1.execute-api.us-east-1.amazonaws.com/alpha/", + "spider": "https://xuvz1oj1sk.execute-api.us-east-1.amazonaws.com/dev/" + }, + "production": { + "audrey": "https://iiouau5d2k.execute-api.us-east-1.amazonaws.com/production/", + "auth": "https://e2we3nktl4.execute-api.us-east-1.amazonaws.com/production/", + "content": "https://32hho6rvg1.execute-api.us-east-1.amazonaws.com/production/", + "megaphone": "https://yfnamdpnc8.execute-api.us-east-1.amazonaws.com/production/", + "moderation": "https://egfpoo3mw3.execute-api.us-east-1.amazonaws.com/production/", + "spider": "https://coi5kvgypc.execute-api.us-east-1.amazonaws.com/production/" + }, } class APIClient(BaseModel): # Internal State (Not passed in __init__) @@ -26,7 +36,14 @@ class APIClient(BaseModel): _access_token: str = PrivateAttr(default="") _access_token_timeout: int = PrivateAttr(default=0) - + def __init__(self, **data): + super().__init__(**data) + self.validate_environment() + + def validate_environment(self): + if self.environment not in endpoints: + raise ValueError(f"Environment '{self.environment}' is not valid. Must be one of: {list(endpoints.keys())}") + def make_request(self, method: str, endpoint: str, path: str, data: dict = None, authenticate: bool = True, query_params: dict = None) -> dict: # Make sure the endpoint is defined in the endpoints dictionary @@ -112,6 +129,17 @@ class APIClient(BaseModel): self._access_token = token_data.get("access_token", "") self._access_token_timeout = token_data.get("access_token_timeout", 0) + def set_environment(self, environment: str) -> None: + """ + Set the environment for the API client. + + Args: + environment (str): The environment to set (e.g., "alpha", "production"). + """ + + self.validate_environment() + self.environment = environment + def is_token_valid(self) -> bool: """ Checks if the current access token is still valid based on the current time and the token's expiration time. diff --git a/tests/apiclient.py b/tests/apiclient.py index 81f24f0..e34b38a 100644 --- a/tests/apiclient.py +++ b/tests/apiclient.py @@ -46,6 +46,25 @@ class TestAPIClient(unittest.TestCase): result = self.api_client.run_job(mock_job, "value1") self.assertEqual(result, "Job executed with value1") + + + def test_set_production_env(self): + Prod_API = APIClient( + client_id="prod_client_id", + client_secret="prod_client_secret", + debug=False, + environment="production" + ) + self.assertEqual(Prod_API.environment, "production") + + def test_set_bad_env(self): + with self.assertRaises(ValueError): + APIClient( + client_id="test_client_id", + client_secret="test_client_secret", + debug=False, + environment="invalid_env" + ) @patch('trustcafeapiwrapper.apiclient.requests.request') def test_make_request(self, mock_request): From c646e7ddbc7bffdf3c47f6d79e079f11a226191f Mon Sep 17 00:00:00 2001 From: simonwt Date: Tue, 7 Apr 2026 22:38:34 +0100 Subject: [PATCH 2/7] More APIclient testing --- src/trustcafeapiwrapper/apiclient.py | 1 - tests/apiclient.py | 21 +++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/trustcafeapiwrapper/apiclient.py b/src/trustcafeapiwrapper/apiclient.py index 6e31fcb..492e255 100644 --- a/src/trustcafeapiwrapper/apiclient.py +++ b/src/trustcafeapiwrapper/apiclient.py @@ -31,7 +31,6 @@ class APIClient(BaseModel): client_id: SecretStr client_secret: SecretStr - _access_token: str = PrivateAttr(default="") _access_token_timeout: int = PrivateAttr(default=0) diff --git a/tests/apiclient.py b/tests/apiclient.py index e34b38a..fa7ec3a 100644 --- a/tests/apiclient.py +++ b/tests/apiclient.py @@ -65,11 +65,24 @@ class TestAPIClient(unittest.TestCase): debug=False, environment="invalid_env" ) - + + def test_set_environment_method(self): + self.api_client.set_environment("production") + self.assertEqual(self.api_client.environment, "production") + + def test_make_non_supported_request_type(self): + with self.assertRaises(ValueError): + self.api_client.make_request("PATCH", "content", "test") + + def test_make_request_with_bad_endpoint(self): + with self.assertRaises(ValueError): + self.api_client.make_request("GET", "invalid_endpoint", "test") + + @patch('trustcafeapiwrapper.apiclient.requests.request') def test_make_request(self, mock_request): - # This is a placeholder test. In a real test, you'd mock the HTTP request and response. - - response = self.api_client.make_request("GET", "content", "https://api.example.com/test") + # This test should be expanded + # Or the the functions should be broken up more to be more easily testable + response = self.api_client.make_request("GET", "content", "test") # mock_request.assert_called_once() \ No newline at end of file From a42a3b65d9cd567d7fcb129f0039ff8eeec734b0 Mon Sep 17 00:00:00 2001 From: simonwt Date: Tue, 7 Apr 2026 23:37:55 +0100 Subject: [PATCH 3/7] Post update --- src/trustcafeapiwrapper/jobs/post/__init__.py | 3 +- src/trustcafeapiwrapper/jobs/post/update.py | 12 ++++++ .../wrappers/post/__init__.py | 3 +- .../wrappers/post/update_post.py | 37 +++++++++++++++++++ testing.py | 33 +++++++++++++---- tests/utils/get_child_spksk_from_paths.py | 4 +- tests/wrappers/react.py | 10 ++--- tests/wrappers/update_post.py | 30 +++++++++++++++ unittests.py | 1 + 9 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 src/trustcafeapiwrapper/jobs/post/update.py create mode 100644 src/trustcafeapiwrapper/wrappers/post/update_post.py create mode 100644 tests/wrappers/update_post.py diff --git a/src/trustcafeapiwrapper/jobs/post/__init__.py b/src/trustcafeapiwrapper/jobs/post/__init__.py index 6621076..d72a28d 100644 --- a/src/trustcafeapiwrapper/jobs/post/__init__.py +++ b/src/trustcafeapiwrapper/jobs/post/__init__.py @@ -1,7 +1,8 @@ +from .update import update +from .create import create from .get import get from .listbybranch import listbybranch from .listbyuserprofile import listbyuserprofile -from .create import create from .listall import listall from .listpublic import listpublic from .listremoved import listremoved \ No newline at end of file diff --git a/src/trustcafeapiwrapper/jobs/post/update.py b/src/trustcafeapiwrapper/jobs/post/update.py new file mode 100644 index 0000000..80f558d --- /dev/null +++ b/src/trustcafeapiwrapper/jobs/post/update.py @@ -0,0 +1,12 @@ +def update(API, payload: dict) -> dict: + """ + Updates an existing post in the API. + + Args: + payload (dict): The data for the post update. + + Returns: + dict: The post data. + """ + post_data = API.make_request("PUT", "content", "post/update", data=payload, authenticate=True) + return post_data \ No newline at end of file diff --git a/src/trustcafeapiwrapper/wrappers/post/__init__.py b/src/trustcafeapiwrapper/wrappers/post/__init__.py index 61a7e0b..eafe74e 100644 --- a/src/trustcafeapiwrapper/wrappers/post/__init__.py +++ b/src/trustcafeapiwrapper/wrappers/post/__init__.py @@ -1 +1,2 @@ -from .create_post import create_post \ No newline at end of file +from .create_post import create_post +from .update_post import update_post \ No newline at end of file diff --git a/src/trustcafeapiwrapper/wrappers/post/update_post.py b/src/trustcafeapiwrapper/wrappers/post/update_post.py new file mode 100644 index 0000000..d302aab --- /dev/null +++ b/src/trustcafeapiwrapper/wrappers/post/update_post.py @@ -0,0 +1,37 @@ +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): + """ + Updates an existing post. + + Args: + post_slug (str): The slug of the post to update. + post_text (str): The new text for the post. + parent_path (str, optional): The parent path for the post. Defaults to '/'. + 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. + + Returns: + dict: The updated post data. + """ + parent_pksk = get_parent_pksk_from_path(parent_path) + post_pksk = get_post_pksk(parent_pksk, post_path) + + 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, + + } + + return { + "job_function": "post.update", + "payload": payload + } \ No newline at end of file diff --git a/testing.py b/testing.py index 5ede80d..ee63098 100644 --- a/testing.py +++ b/testing.py @@ -61,9 +61,9 @@ def save_response(response): # print("-----------------------------") # feed = API.run_job('post.listbybranch', "music") # print(feed) -print("-----------------------------") -feed = save_response(API.run_job('post.listremoved')) -print(feed) +# print("-----------------------------") +# feed = save_response(API.run_job('post.listremoved')) +# print(feed) # print("----------------------------z # branchlist = API.run_job('branch.listbyname') # print(branchlist) @@ -80,6 +80,16 @@ print(feed) # print(feed) # print("-----------------------------") +# save_response(API.run_job('post.create', { +# "blurLabel": None, +# "cardUrl": None, +# "postText": "This is a test post created via the API wrapper.", +# "collaborative": False, +# "parent": { +# "pk": "maintrunk#maintrunk", +# "sk": "maintrunk#maintrunk" +# } +# })) # save_response(API.run_job('post.create', { # "blurLabel": None, # "cardUrl": None, @@ -183,8 +193,15 @@ print(feed) # users = API.run_job('trust.listbyuserhas', "simon-little ") # print(users) -from trustcafeapiwrapper.wrappers.trust import trust -save_response(API.wrapped(trust( - 100, - "/user/bossman" -))) \ No newline at end of file +# from trustcafeapiwrapper.wrappers.trust import trust +# save_response(API.wrapped(trust( +# 100, +# "/user/bossman" +# ))) + +from trustcafeapiwrapper.wrappers.post.update_post import update_post +save_response(API.wrapped(update_post( + post_text="This is an updated version of the test post created via the create_post wrapper function.", + post_path="/post/1775143460-ef45186a", + parent_path="/", +))) diff --git a/tests/utils/get_child_spksk_from_paths.py b/tests/utils/get_child_spksk_from_paths.py index b639c6d..f45526b 100644 --- a/tests/utils/get_child_spksk_from_paths.py +++ b/tests/utils/get_child_spksk_from_paths.py @@ -13,10 +13,10 @@ class TestGetChildSpkskFromPaths(unittest.TestCase): self.assertEqual(get_child_spksk_from_paths(parent_path, item_path), expected_output) def test_comment_reaction(self): - parent_path = '/post/12345' + parent_path = '/post/12345-abcv' item_path = '/comment/67890' expected_output = { - "pk": "post#12345", + "pk": "post#12345-abcv", "sk": "comment#67890", "entity": "comment", "slug": "67890" diff --git a/tests/wrappers/react.py b/tests/wrappers/react.py index 52598f3..641a535 100644 --- a/tests/wrappers/react.py +++ b/tests/wrappers/react.py @@ -5,7 +5,7 @@ class TestReact(unittest.TestCase): def test_react_to_post(self): reaction_type = 'like' parent_path = '/' - item_path = '/post/12345' + item_path = '/post/12345-abcv' result = react(reaction_type, parent_path, item_path) self.assertIsInstance(result, dict) @@ -14,13 +14,13 @@ class TestReact(unittest.TestCase): self.assertEqual(result["job_function"], "reaction.reacttosomething") self.assertEqual(result["payload"]["reaction"], reaction_type) self.assertEqual(result["payload"]["parent"]["pk"], "maintrunk#maintrunk") - self.assertEqual(result["payload"]["parent"]["sk"], "post#12345") + self.assertEqual(result["payload"]["parent"]["sk"], "post#12345-abcv") self.assertEqual(result["payload"]["parent"]["entity"], "post") - self.assertEqual(result["payload"]["parent"]["slug"], "12345") + self.assertEqual(result["payload"]["parent"]["slug"], "12345-abcv") def test_react_to_comment(self): reaction_type = 'like' - parent_path = '/post/12345' + parent_path = '/post/12345-abcv' item_path = '/comment/67890' result = react(reaction_type, parent_path, item_path) @@ -29,7 +29,7 @@ class TestReact(unittest.TestCase): self.assertIn("payload", result) self.assertEqual(result["job_function"], "reaction.reacttosomething") self.assertEqual(result["payload"]["reaction"], reaction_type) - self.assertEqual(result["payload"]["parent"]["pk"], "post#12345") + self.assertEqual(result["payload"]["parent"]["pk"], "post#12345-abcv") self.assertEqual(result["payload"]["parent"]["sk"], "comment#67890") self.assertEqual(result["payload"]["parent"]["entity"], "comment") self.assertEqual(result["payload"]["parent"]["slug"], "67890") \ No newline at end of file diff --git a/tests/wrappers/update_post.py b/tests/wrappers/update_post.py new file mode 100644 index 0000000..03c0d36 --- /dev/null +++ b/tests/wrappers/update_post.py @@ -0,0 +1,30 @@ +import unittest +from trustcafeapiwrapper.wrappers.post.update_post import update_post +class TestUpdatePost(unittest.TestCase): + def setUp(self): + self.post_text = "This is an updated test post created via the update_post wrapper function." + self.blur_label = None + self.card_url = None + self.collaborative = False + + def test_update_post(self): + 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=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"]["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"]) \ No newline at end of file diff --git a/unittests.py b/unittests.py index f787956..d0dc1b9 100644 --- a/unittests.py +++ b/unittests.py @@ -11,6 +11,7 @@ from tests.utils.get_user_slug_from_path import TestGetUserSlugFromPath from tests.utils.get_userprofile_pksk_from_slug import TestGetUserprofilePkskFromSlug from tests.wrappers.create_post import TestCreatePost +from tests.wrappers.update_post import TestUpdatePost from tests.wrappers.create_comment import TestCreateComment from tests.wrappers.react import TestReact from tests.wrappers.vote import TestVoteCast From adef4fa8aa1e191f2c52472f92602ce859466add Mon Sep 17 00:00:00 2001 From: simonwt Date: Wed, 8 Apr 2026 12:17:26 +0100 Subject: [PATCH 4/7] testing removed --- testing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing.py b/testing.py index bd8f217..58823e3 100644 --- a/testing.py +++ b/testing.py @@ -61,9 +61,9 @@ def save_response(response): # print("-----------------------------") # feed = API.run_job('post.listbybranch', "music") # print(feed) -print("-----------------------------") -feed = save_response(API.run_job('post.listremoved')) -print(feed) +# print("-----------------------------") +# feed = save_response(API.run_job('post.listremoved')) +# print(feed) # print("----------------------------z # branchlist = API.run_job('branch.listbyname') # print(branchlist) From ae6ee7e7a0b37b24dd27bb7815d1c3ed8b278400 Mon Sep 17 00:00:00 2001 From: simonwt Date: Thu, 9 Apr 2026 00:34:23 +0100 Subject: [PATCH 5/7] Fix public Add guest alternative Write some doc --- documentation.md | 81 ++++++++++++++++++- src/trustcafeapiwrapper/apiclient.py | 13 +-- .../jobs/post/listpublic.py | 2 +- testing.py | 14 ++-- 4 files changed, 94 insertions(+), 16 deletions(-) diff --git a/documentation.md b/documentation.md index 7e01c84..4f768d9 100644 --- a/documentation.md +++ b/documentation.md @@ -1,18 +1,93 @@ # Basic usage (without an .env) +## Setup Here's how to make a post to the homepage (aka Maintrunk) ```python import trustcafeapiwrapper -from trustcafeapiwrapper.wrappers.post.create_post import create_post # Setup the API Client API = trustcafeapiwrapper.APIClient( client_id="YOUR_CLIENT_ID" client_secret="YOUR_CLIENT_SECRET" ) +``` +## Use a wrapper +Wrappers make it as simple as possible to perform an action. + +A wrapper will come with a job and a payload ready to be passed to the `wrapped` function. + +eg. Use the `create_post` wrapper + +```python +from trustcafeapiwrapper.wrappers.post.create_post import create_post -# Use the create_post wrapper API.wrapped(create_post( "This is a test post created via the create_post wrapper function.", )) -``` \ No newline at end of file +``` + + +**_NOTE:_** There are not as many wrappers as jobs because fetching is simpler. More will be added though to make it consistent. + + +## Make a request via `job` +Jobs make the requests to the API. They have the endpoint and path setup already, we just pass the payload. If you want to avoid making your own payload, use a `wrapper`. + +Use the job for getting public posts. + +Now you don't need to worry about which API +```python +postlist = API.run_job('posts.getpublic') +print(postlist) +''' +{ + "Items": [ + { + ... +''' +``` +**_NOTE:_** There are aren't the complete set yet created here. + +## Custom Request +There should hopefully be a `job` or a `wrapper` that exists for what you need but if not, then this is how you make a request using the core mechanism. + +Use the `make_request` function to make a request from the content API to get posts in the music branch. +```python +postlist = API.make_request("GET", "content", f"post/ref-subwiki/music") +print(postlist) +''' +{ + "Items": [ + { + ... +''' +``` + +# Jobs +## block +## branch +### `get` +### `listbyname` +## change +## comment +### `create` +### `listbypostid` +## feed +## mute +## notifcation +### `listnotifications` +## post +## reaction +## trust +## userprofile +## vote + + +# Wrappers +## post +### `create_post` +### `update_post` +## comment +### `create_comment` + +# Utils \ No newline at end of file diff --git a/src/trustcafeapiwrapper/apiclient.py b/src/trustcafeapiwrapper/apiclient.py index 492e255..ab25a19 100644 --- a/src/trustcafeapiwrapper/apiclient.py +++ b/src/trustcafeapiwrapper/apiclient.py @@ -61,13 +61,16 @@ class APIClient(BaseModel): url += f"?{urlencode(query_params)}" # Set up headers for the request - headers = { - "Content-Type": "application/json", - } - # Add the Authorization header with the access token if authentication is required if authenticate: token = self._access_token - headers["Authorization"] = f"Bearer {token}" + else: + token = 'guest' + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token}" + } + # Debugging output to show the request details before making the API call if self.debug: diff --git a/src/trustcafeapiwrapper/jobs/post/listpublic.py b/src/trustcafeapiwrapper/jobs/post/listpublic.py index 3dee463..1f9c702 100644 --- a/src/trustcafeapiwrapper/jobs/post/listpublic.py +++ b/src/trustcafeapiwrapper/jobs/post/listpublic.py @@ -7,5 +7,5 @@ def listpublic(API, lastEvaluatedKey=None) -> dict: Returns: dict: The list of public posts. """ - post_list = API.make_request("GET", "content", f"post/public", authenticate=True, query_params=lastEvaluatedKey) + post_list = API.make_request("GET", "content", f"public/posts", authenticate=False, query_params=lastEvaluatedKey) return post_list \ No newline at end of file diff --git a/testing.py b/testing.py index ee63098..57f2ab5 100644 --- a/testing.py +++ b/testing.py @@ -149,7 +149,7 @@ def save_response(response): # })) # save_response(API.run_job('post.listall')) -# save_response(API.run_job('post.listpublic')) +save_response(API.run_job('post.listpublic')) # from trustcafeapiwrapper.wrappers.post.create_post import create_post @@ -199,9 +199,9 @@ def save_response(response): # "/user/bossman" # ))) -from trustcafeapiwrapper.wrappers.post.update_post import update_post -save_response(API.wrapped(update_post( - post_text="This is an updated version of the test post created via the create_post wrapper function.", - post_path="/post/1775143460-ef45186a", - parent_path="/", -))) +# from trustcafeapiwrapper.wrappers.post.update_post import update_post +# save_response(API.wrapped(update_post( +# post_text="This is an updated version of the test post created via the create_post wrapper function.", +# post_path="/post/1775143460-ef45186a", +# parent_path="/", +# ))) From 72a49285c12149f91eb78b4c0573dc430bc27440 Mon Sep 17 00:00:00 2001 From: simonwt Date: Thu, 9 Apr 2026 00:36:47 +0100 Subject: [PATCH 6/7] Version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f164da6..95e3b1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "trustcafeapiwrapper" -version = "0.1.0.6" +version = "0.1.0.7" description = "Wraps the Trust Cafe API" readme = "README.md" requires-python = ">=3.13" From 921b6f5d95b70f0690e88008278bdcf004ad1d7c Mon Sep 17 00:00:00 2001 From: simonwt Date: Thu, 9 Apr 2026 00:41:48 +0100 Subject: [PATCH 7/7] docs --- documentation.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/documentation.md b/documentation.md index 4f768d9..36027e5 100644 --- a/documentation.md +++ b/documentation.md @@ -1,16 +1,30 @@ # Basic usage (without an .env) ## Setup -Here's how to make a post to the homepage (aka Maintrunk) - +Here's a very basic example of setting up: ```python import trustcafeapiwrapper # Setup the API Client API = trustcafeapiwrapper.APIClient( - client_id="YOUR_CLIENT_ID" - client_secret="YOUR_CLIENT_SECRET" + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + env="alpha" # alpha | production. + debug=False ) ``` +or with .env +```python +import trustcafeapiwrapper, os +from dotenv import load_dotenv +load_dotenv() +API = APIClient( + client_id=os.getenv("client_id"), + client_secret=os.getenv("client_secret"), + env="alpha", # alpha | production. + debug=False, +) +``` + ## Use a wrapper Wrappers make it as simple as possible to perform an action.