From a223bcb27a07132562240de74c7b5ce0d6d59231 Mon Sep 17 00:00:00 2001 From: BarnacleBoy Date: Sat, 18 Apr 2026 22:58:33 +0000 Subject: [PATCH] =?UTF-8?q?doc:=20audit=20pass=20=E2=80=94=20fix=20env=3D?= =?UTF-8?q?=20bug,=20params,=20pagination,=20missing=20wrappers,=20broken?= =?UTF-8?q?=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - env= -> environment= across all docs (Pydantic silently ignores env=) - Remove __version__ check (doesn't exist in module) - Fix APIClient(debug=True) missing required client_id/secret - Fix post.listremoved usage (takes no postId, just lists removed posts) - Fix JS true/false -> Python True/False in code blocks - Fix parameter names: branchName->branch_slug, username->user_slug, postId->post_id - Add missing lastEvaluatedKey param docs for list jobs - Note post.listpublic as unauthenticated endpoint - Fix pagination examples (LastEvaluatedKey, not ExclusiveStartKey dict) - Remove non-existent headers param from make_request example - Fix branch.listbyname example (takes no name arg) - Add 4 missing wrapper docs: follow, react, trust, votecast - Fix broken internal links (case-sensitive filenames) - Fix _handle_token template artifact - Add CODE_BUGS.md documenting 4 code bugs found during audit --- CODE_BUGS.md | 101 +++++++++++++ README.md | 30 ++-- docs/API_REFERENCE.md | 58 ++++---- docs/CUSTOM_REQUESTS.md | 42 +++--- docs/GETTING_STARTED.md | 29 ++-- docs/INDEX.md | 7 +- docs/TROUBLESHOOTING.md | 10 +- docs/WRAPPERS.md | 214 +++++++++++++++++++++++++++- documentation.md | 308 ++++++++++++++++++++-------------------- 9 files changed, 552 insertions(+), 247 deletions(-) create mode 100644 CODE_BUGS.md diff --git a/CODE_BUGS.md b/CODE_BUGS.md new file mode 100644 index 0000000..bd32234 --- /dev/null +++ b/CODE_BUGS.md @@ -0,0 +1,101 @@ +# Code Bugs Found During Documentation Audit + +This file documents bugs discovered in the source code during the documentation audit on April 18, 2026. These are **not** documentation errors — they are issues in the code itself that affect runtime behavior. + +--- + +## 1. `trust.listbyusersinit` — Function Name Mismatch + +**Severity:** High — causes runtime crash + +**File:** `src/trustcafeapiwrapper/jobs/trust/listbyusersinit.py` + +**Problem:** The file is named `listbyusersinit.py`, but the function inside is named `listbyuserinit()`. Additionally, `src/trustcafeapiwrapper/jobs/trust/__init__.py` imports `listbyuserinit` (matching the function name), not `listbyusersinit` (matching the file name). + +**Impact:** Calling `API.run_job('trust.listbyusersinit')` resolves to the *module* object (because Python can import submodules as attributes), not the function. This causes `TypeError: 'module' object is not callable`. + +**Working alternative:** `API.run_job('trust.listbyuserinit', user_id)` — this resolves to the actual function and works correctly. + +**Evidence:** +```python +API.run_job('trust.listbyusersinit') +# TypeError: 'module' object is not callable + +API.run_job('trust.listbyuserinit', 'user123') +# Resolves correctly, makes API call (will fail auth without real credentials, but function resolution works) +``` + +**Fix options:** +- Rename the function from `listbyuserinit` to `listbyusersinit` in `listbyusersinit.py` and update `trust/__init__.py` +- OR rename the file from `listbyusersinit.py` to `listbyuserinit.py` + +--- + +## 2. `APIClient.wrapped()` — Docstring Mismatch + +**Severity:** Low — code works correctly, docstring is wrong + +**File:** `src/trustcafeapiwrapper/apiclient.py`, method `wrapped()` + +**Problem:** The docstring says the method expects `'job'` and `'payload'` keys: +``` +A dictionary with 'job' (string) and 'payload' (dict) keys. +``` +But the actual code reads `job_function`: +```python +return self.run_job(wrapped_data.get("job_function"), wrapped_data.get("payload", {})) +``` + +**Impact:** No runtime impact — all wrapper functions return `{"job_function": ..., "payload": ...}` which matches what `wrapped()` reads. Only the docstring is misleading. + +**Fix:** Update docstring from `'job'` to `'job_function'`. + +--- + +## 3. `env=` Constructor Parameter Silently Ignored + +**Severity:** High — silent wrong-environment bug + +**File:** `src/trustcafeapiwrapper/apiclient.py`, class `APIClient` + +**Problem:** `APIClient` inherits from Pydantic's `BaseModel`. The model defines `environment: str = "alpha"`, but there is no field named `env`. Because Pydantic silently ignores unknown fields by default, passing `env="production"` is **silently discarded** and the environment stays `"alpha"`. + +**Impact:** Any code using `APIClient(client_id=..., client_secret=..., env="production")` will silently run against the alpha environment instead of production. No error, no warning. + +**Evidence:** +```python +API = APIClient(client_id='test', client_secret='test', env='production') +print(API.environment) # Prints "alpha" — NOT "production" + +API = APIClient(client_id='test', client_secret='test', environment='production') +print(API.environment) # Prints "production" — correct +``` + +**Fix options:** +- Add a model validator that raises an error on unknown fields: `model_config = ConfigDict(extra='forbid')` +- OR add `env` as an alias for `environment` + +--- + +## 4. Missing `__version__` Attribute + +**Severity:** Low — not a runtime bug, but a packaging convention + +**File:** `src/trustcafeapiwrapper/__init__.py` + +**Problem:** The module does not expose `__version__`. The `__init__.py` only contains: +```python +from .apiclient import APIClient +``` + +**Impact:** Users cannot programmatically check the installed version via `trustcafeapiwrapper.__version__`. This is a standard Python packaging convention. + +**Fix:** Add to `__init__.py`: +```python +from importlib.metadata import version as _get_version +__version__ = _get_version("trustcafeapiwrapper") +``` + +--- + +*These bugs were found during a documentation accuracy audit. The documentation has been updated to reflect actual code behavior (e.g., using `environment=` instead of `env=`, documenting `trust.listbyuserinit` as the working job name).* diff --git a/README.md b/README.md index 37d0e20..739cfdf 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,13 @@ import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="alpha", # Options: "alpha" or "production" + environment="alpha", # Options: "alpha" or "production" debug=False ) # Authenticate and get user profile API.handle_token() -profile = API.run_job('userprofile.get', "your-username") +profile = API.run_job('userprofile.get', "your-user-slug") print(profile) ``` @@ -112,7 +112,7 @@ def saveMyToken(token_data): API = APIClient( client_id=os.getenv("client_id"), client_secret=os.getenv("client_secret"), - env="production", + environment="production", debug=False ) @@ -144,7 +144,7 @@ The wrapper supports two environments: API = APIClient( client_id="your-client-id", client_secret="your-client-secret", - env="alpha", + environment="alpha", debug=False ) @@ -152,7 +152,7 @@ API = APIClient( API = APIClient( client_id="your-client-id", client_secret="your-client-secret", - env="production", + environment="production", debug=False ) @@ -200,8 +200,8 @@ updated_post = API.run_job('post.update', { "newPostText": "Updated text" }) -# Delete a post -deleted = API.run_job('post.listremoved', "post-id") +# List removed posts +removed = API.run_job('post.listremoved') ``` #### Comment Operations @@ -224,7 +224,7 @@ comments = API.run_job('comment.listtbypostid', "post-id") ```python # Get user profile -profile = API.run_job('userprofile.get', "username") +profile = API.run_job('userprofile.get', "user-slug") ``` #### Follow Operations @@ -232,7 +232,7 @@ profile = API.run_job('userprofile.get', "username") ```python # Follow a user followed = API.run_job('follow.follow', { - "isFollowing": true, + "isFollowing": True, "parent": { "pk": "userprofile#username", "sk": "userprofile#username" @@ -427,7 +427,7 @@ Enable debug mode to see all API requests and responses: API = APIClient( client_id=os.getenv("client_id"), client_secret=os.getenv("client_secret"), - env="production", + environment="production", debug=True # Enable debug mode ) @@ -447,7 +447,7 @@ import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="alpha", + environment="alpha", debug=False ) @@ -475,12 +475,12 @@ import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="production" + environment="production" ) # Follow a user follow = API.run_job('follow.follow', { - "isFollowing": true, + "isFollowing": True, "parent": { "pk": "userprofile#philosopher-jon", "sk": "userprofile#philosopher-jon" @@ -504,7 +504,7 @@ import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="production" + environment="production" ) API.handle_token() @@ -527,7 +527,7 @@ import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="production" + environment="production" ) API.handle_token() diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index 3430824..ec2ddc6 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -31,7 +31,7 @@ import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="alpha", # or "production" + environment="alpha", # or "production" debug=False ) @@ -159,7 +159,8 @@ Lists all posts. posts = API.run_job('post.listall') ``` -**Parameters:** None +**Parameters:** +- `lastEvaluatedKey` (dict, optional): Pagination key for next page **Returns:** dict - List of all posts @@ -173,13 +174,14 @@ print(f"Total posts: {len(all_posts.get('Items', []))}") ### post.listpublic() -Lists public posts only. +Lists public posts only. This endpoint does not require authentication. ```python public_posts = API.run_job('post.listpublic') ``` -**Parameters:** None +**Parameters:** +- `lastEvaluatedKey` (dict, optional): Pagination key for next page **Returns:** dict - List of public posts @@ -197,11 +199,12 @@ for post in public_posts.get('Items', []): Lists posts in a specific branch. ```python -branch_posts = API.run_job('post.listbybranch', branch_name) +branch_posts = API.run_job('post.listbybranch', branch_slug) ``` **Parameters:** -- `branchName` (str): The branch identifier +- `branch_slug` (str): The branch/subwiki slug identifier +- `lastEvaluatedKey` (dict, optional): Pagination key for next page **Returns:** dict - List of posts in the branch @@ -217,11 +220,12 @@ music_posts = API.run_job('post.listbybranch', "music") Lists posts by a specific user profile. ```python -user_posts = API.run_job('post.listbyuserprofile', username) +user_posts = API.run_job('post.listbyuserprofile', user_slug) ``` **Parameters:** -- `username` (str): The username of the user +- `user_slug` (str): The user profile slug +- `lastEvaluatedKey` (dict, optional): Pagination key for next page **Returns:** dict - List of posts by user @@ -237,17 +241,19 @@ journals = API.run_job('post.listbyuserprofile', "philosopher-jon") Lists removed/deleted posts. ```python -removed = API.run_job('post.listremoved', post_id) +removed = API.run_job('post.listremoved') ``` **Parameters:** -- `postId` (str): The post identifier +- `lastEvaluatedKey` (dict, optional): Pagination key for next page -**Returns:** dict - Removed post data +**Returns:** dict - List of removed posts **Example:** ```python -removed_post = API.run_job('post.listremoved', "deleted-post-id") +removed_posts = API.run_job('post.listremoved') +for post in removed_posts.get('Items', []): + print(post.get('pk')) ``` --- @@ -295,7 +301,7 @@ comments = API.run_job('comment.listtbypostid', post_id) ``` **Parameters:** -- `postId` (str): The post ID +- `post_id` (str): The post ID **Returns:** dict - List of comments @@ -360,7 +366,7 @@ result = API.run_job('follow.follow', payload) ```python # Follow a user followed = API.run_job('follow.follow', { - "isFollowing": true, + "isFollowing": True, "parent": { "pk": "userprofile#philosopher-jon", "sk": "userprofile#philosopher-jon" @@ -368,9 +374,9 @@ followed = API.run_job('follow.follow', { "followType": "userprofile", "parentSlug": "philosopher-jon", "preferences": { - "notification": true, - "emailNew": false, - "emailDigest": true + "notification": True, + "emailNew": False, + "emailDigest": True } }) ``` @@ -796,20 +802,20 @@ For large result sets, results may include pagination: ```python def get_all_posts(): posts = [] - paginator = None + last_key = None while True: - response = API.run_job('post.listpublic', params) + if last_key: + response = API.run_job('post.listpublic', last_key) + else: + response = API.run_job('post.listpublic') posts.extend(response.get('Items', [])) has_more = 'LastEvaluatedKey' in response if not has_more: break - # Set params for next page - params = { - 'ExclusiveStartKey': response['LastEvaluatedKey'] - } + last_key = response['LastEvaluatedKey'] return posts ``` @@ -877,6 +883,6 @@ Implement rate limiting for production use (see main README for example). ## Next Steps -- [Wrappers Guide](wrappers.md) - High-level wrappers for common tasks -- [Custom Requests Guide](custom_requests.md) - Making advanced API calls -- [Troubleshooting Guide](troubleshooting.md) - Common issues and solutions +- [Wrappers Guide](WRAPPERS.md) - High-level wrappers for common tasks +- [Custom Requests Guide](CUSTOM_REQUESTS.md) - Making advanced API calls +- [Troubleshooting Guide](TROUBLESHOOTING.md) - Common issues and solutions diff --git a/docs/CUSTOM_REQUESTS.md b/docs/CUSTOM_REQUESTS.md index 91056b9..9f950c4 100644 --- a/docs/CUSTOM_REQUESTS.md +++ b/docs/CUSTOM_REQUESTS.md @@ -51,7 +51,7 @@ import os API = trustcafeapiwrapper.APIClient( client_id=os.getenv("CLIENT_ID"), client_secret=os.getenv("CLIENT_SECRET"), - env="alpha", + environment="alpha", debug=True ) @@ -172,17 +172,16 @@ response = API.make_request( ) ``` -### Manual token injection (advanced): +### Manual token handling (advanced): ```python -# Sometimes you may need to bypass the token management +# If you need to bypass token management, set the access_token directly on the API instance +API.access_token = "your-token-manually-set" + response = API.make_request( "GET", "content", - "post/some-path", - headers={ - "Authorization": "Bearer your-token-manually-set" - } + "post/some-path" ) ``` @@ -194,30 +193,25 @@ TrustCafé responses may include pagination for large result sets: ```python def get_all_posts(limit=100): - """Fetch all posts with pagination""" + """Fetch all posts with pagination using make_request directly.""" - def fetch_page(start_key=None): + def fetch_page(last_evaluated_key=None): """Fetch a single page of posts""" - params = {"Limit": limit} - - if start_key: - params["ExclusiveStartKey"] = start_key - response = API.make_request( "GET", "content", - "post/listall", - query_params=params, + "post", + query_params=last_evaluated_key, authenticate=True ) return response all_posts = [] - start_key = None + last_key = None while True: - page = fetch_page(start_key) + page = fetch_page(last_key) items = page.get('Items', []) all_posts.extend(items) @@ -227,15 +221,15 @@ def get_all_posts(limit=100): if not has_more or len(items) == 0: break - # Set start key for next page - start_key = page['LastEvaluatedKey'] + # Pass the LastEvaluatedKey directly as query_params for next page + last_key = page['LastEvaluatedKey'] return all_posts # Usage API.handle_token() -all_posts = get_all_posts(limit=50) +all_posts = get_all_posts() print(f"Total posts: {len(all_posts)}") ``` @@ -761,7 +755,7 @@ def conditional_update_post(post_id, updates, condition=None): ## Next Steps -- [API Reference](api_reference.md) - Complete list of jobs -- [Wrappers Guide](wrappers.md) - High-level wrappers for common operations +- [API Reference](API_REFERENCE.md) - Complete list of jobs +- [Wrappers Guide](WRAPPERS.md) - High-level wrappers for common operations - [Examples](../README.md#examples) - Real-world usage examples -- [Troubleshooting](troubleshooting.md) - Common issues and solutions +- [Troubleshooting](TROUBLESHOOTING.md) - Common issues and solutions diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 080628f..365c3f8 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -36,7 +36,10 @@ uv add trustcafeapiwrapper ```python import trustcafeapiwrapper -print(trustcafeapiwrapper.__version__) # Should print the version + +# Verify the module imported successfully +from trustcafeapiwrapper import APIClient +print("trustcafeapiwrapper installed successfully") ``` ## API Credentials Setup @@ -120,7 +123,7 @@ The TrustCafé API wrapper supports two environments: API = APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="alpha" # Alpha environment + environment="alpha" # Alpha environment ) ``` @@ -134,7 +137,7 @@ API = APIClient( API = APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="production" # Production environment + environment="production" # Production environment ) ``` @@ -142,7 +145,7 @@ API = APIClient( ```python # Initialize with alpha -API = APIClient(client_id="id", client_secret="secret", env="alpha") +API = APIClient(client_id="id", client_secret="secret", environment="alpha") # Later, switch to production API.set_environment("production") @@ -162,7 +165,7 @@ load_dotenv() API = trustcafeapiwrapper.APIClient( client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), - env="alpha", # or "production" + environment="alpha", # or "production" debug=True # Set to True to see request/response details ) @@ -307,7 +310,7 @@ Always test in alpha before production: ```python # Test with alpha first -API = APIClient(client_id="test-id", client_secret="test-secret", env="alpha") +API = APIClient(client_id="test-id", client_secret="test-secret", environment="alpha") # Verify token works results = API.handle_token() @@ -317,7 +320,7 @@ print("Alpha environment works!") API = APIClient( client_id=os.getenv("PROD_CLIENT_ID"), client_secret=os.getenv("PROD_CLIENT_SECRET"), - env="production" + environment="production" ) ``` @@ -369,7 +372,7 @@ Use debug mode during development: API = APIClient( client_id="your-id", client_secret="your-secret", - env="internal", + environment="alpha", # "alpha" or "production" debug=True # Prints request/response details ) ``` @@ -454,16 +457,16 @@ LOG_LEVEL=INFO Now that you're set up: 1. **Read the API Reference**: Learn about all available jobs and wrappers - - [API Reference](api_reference.md) + - [API Reference](API_REFERENCE.md) 2. **Explore Wrappers**: Try the high-level wrappers for common tasks - - [Wrappers Guide](wrappers.md) + - [Wrappers Guide](WRAPPERS.md) 3. **Make Custom Requests**: Learn to make advanced API calls manually - - [Custom Requests Guide](custom_requests.md) + - [Custom Requests Guide](CUSTOM_REQUESTS.md) 4. **Handle Errors**: Learn about common errors and how to handle them - - [Troubleshooting Guide](troubleshooting.md) + - [Troubleshooting Guide](TROUBLESHOOTING.md) 5. **Contribute**: Help complete the wrapper with additional jobs - - [Development Guide](development.md) + - [Development Guide](../development.md) diff --git a/docs/INDEX.md b/docs/INDEX.md index 98354b8..7570a84 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -67,6 +67,7 @@ Complete documentation for the TrustCafé API wrapper. - Overview of wrapper system - Post wrapper documentation (create_post, update_post) - Comment wrapper documentation (create_comment) +- Interaction wrappers (follow, react, trust, votecast) - Wrapper best practices - Wrapper vs Job comparison - Creating custom wrappers @@ -126,16 +127,16 @@ If you can't find the answer you're looking for: Document version updates should be listed here. -### v0.2.0 (Current) +### v0.2.0 (Current Documentation) - Complete rewrite and enhancement of all documentation - Added full API reference -- Added wrappers guide +- Added wrappers guide (including follow, react, trust, votecast) - Added custom requests guide - Added detailed troubleshooting section - Enhanced development documentation - Improved README.md structure and examples -### v0.1.0.13 +### v0.1.0.13 (Package Version / Initial Release) - Initial release with basic documentation - Minimum set of jobs and wrappers diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 523127f..efa4e1c 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -214,7 +214,7 @@ FileNotFoundError: Token data file 'token_data_alpha.json' not found **Solutions:** -1. API `_handle_token` automatically creates file on first use +1. API `handle_token` automatically creates file on first use 2. Ensure working directory is writable @@ -346,7 +346,7 @@ ValueError: Invalid parameter or data format 4. Use debug mode to see request: ```python - API = APIClient(debug=True) + API = APIClient(client_id="your-id", client_secret="your-secret", debug=True) # Now you'll see the exact data being sent API.run_job('post.create', data) @@ -389,7 +389,7 @@ FileNotFoundError: Endpoint not found 4. Use debug mode to see exact path being sent: ```python - API = APIClient(debug=True) + API = APIClient(client_id="your-id", client_secret="your-secret", debug=True) API.run_job('post.create', data) # See exact request path ``` @@ -493,7 +493,7 @@ ValueError: Environment 'staging' is not valid. Must be one of: ['alpha', 'produ API_prod = APIClient( client_id="prod-id", client_secret="prod-secret", - env="production" + environment="production" ) # Old API still has old environment @@ -663,7 +663,7 @@ requests.exceptions.Timeout: Request timed out API = APIClient( client_id="your-id", client_secret="your-secret", - env="alpha", + environment="alpha", debug=True # Enables verbose logging ) ``` diff --git a/docs/WRAPPERS.md b/docs/WRAPPERS.md index b0aa4af..315c212 100644 --- a/docs/WRAPPERS.md +++ b/docs/WRAPPERS.md @@ -7,6 +7,7 @@ This guide covers the high-level wrapper functions provided by the TrustCafé AP - [Overview](#overview) - [Post Wrappers](#post-wrappers) - [Comment Wrappers](#comment-wrappers) +- [Interaction Wrappers](#interaction-wrappers) - [Wrappers Best Practices](#wrappers-best-practices) - [Wrapper vs Job](#wrapper-vs-job) @@ -308,6 +309,201 @@ API.wrapped(create_comment( )) ``` +## Interaction Wrappers + +### follow() + +Follow or unfollow a user or branch. + +```python +from trustcafeapiwrapper.wrappers.follow.follow import follow + +API.wrapped(follow( + entity="userprofile", + is_following=True, + parent_slug="philosopher-jon" +)) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `entity` | str | **Required** | Type of entity to follow: `"userprofile"` or `"subwiki"` | +| `is_following` | bool | **Required** | `True` to follow, `False` to unfollow | +| `parent_slug` | str | **Required** | Slug of the entity to follow/unfollow | + +**Returns:** dict - Job execution result + +**Example - Follow a user:** + +```python +from trustcafeapiwrapper.wrappers.follow.follow import follow + +API.wrapped(follow( + entity="userprofile", + is_following=True, + parent_slug="philosopher-jon" +)) +``` + +**Example - Unfollow a branch:** + +```python +API.wrapped(follow( + entity="subwiki", + is_following=False, + parent_slug="music" +)) +``` + +--- + +### react() + +React to a post or comment (like, heart, etc.). This single endpoint handles creating, updating, and deleting reactions — the logic is handled server-side. + +```python +from trustcafeapiwrapper.wrappers.reaction.react import react + +API.wrapped(react( + reaction_type="like", + parent_path="/music", + item_path="/music/my-post-slug" +)) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `reaction_type` | str | **Required** | Reaction type (e.g., `"like"`, `"heart"`, `"fire"`) | +| `parent_path` | str | `None` | Parent path (e.g., `"/music"`) | +| `item_path` | str | `None` | Item path (e.g., `"/music/my-post-slug"`) | +| `item_key` | dict | `None` | Dict with `pk` and `sk` keys | + +**Returns:** dict - Job execution result + +**Note:** You must provide either: +- Both `parent_path` and `item_path`, or +- The `item_key` dict with `pk` and `sk` + +**Example - React by paths:** + +```python +from trustcafeapiwrapper.wrappers.reaction.react import react + +API.wrapped(react( + reaction_type="like", + parent_path="/music", + item_path="/music/my-post-slug" +)) +``` + +**Example - React by key:** + +```python +API.wrapped(react( + reaction_type="heart", + item_key={ + "pk": "post#my-post-slug", + "sk": "post#my-post-slug" + } +)) +``` + +--- + +### trust() + +Set trust level for a user profile. + +```python +from trustcafeapiwrapper.wrappers.trust.trust import trust + +API.wrapped(trust( + trustLevel="trusted", + userprofile_path="/userprofile/philosopher-jon" +)) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `trustLevel` | str | **Required** | Trust level: `"trusted"`, `"neutral"`, or `"distrusted"` | +| `userprofile_path` | str | **Required** | Path to the user profile (e.g., `"/userprofile/slug"`) | + +**Returns:** dict - Job execution result + +**Example:** + +```python +from trustcafeapiwrapper.wrappers.trust.trust import trust + +API.wrapped(trust( + trustLevel="trusted", + userprofile_path="/userprofile/philosopher-jon" +)) +``` + +--- + +### votecast() + +Cast a vote on a post or comment. + +```python +from trustcafeapiwrapper.wrappers.vote.votecast import votecast + +API.wrapped(votecast( + vote="up", + parent_path="/music", + item_path="/music/my-post-slug" +)) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `vote` | str | **Required** | Vote value (e.g., `"up"`, `"down"`) | +| `parent_path` | str | `None` | Parent path | +| `item_path` | str | `None` | Item path | +| `item_key` | dict | `None` | Dict with `pk` and `sk` keys | + +**Returns:** dict - Job execution result + +**Note:** You must provide either: +- Both `parent_path` and `item_path`, or +- The `item_key` dict with `pk` and `sk` + +**Example - Vote by paths:** + +```python +from trustcafeapiwrapper.wrappers.vote.votecast import votecast + +API.wrapped(votecast( + vote="up", + parent_path="/music", + item_path="/music/my-post-slug" +)) +``` + +**Example - Vote by key:** + +```python +API.wrapped(votecast( + vote="down", + item_key={ + "pk": "post#my-post-slug", + "sk": "post#my-post-slug" + } +)) +``` + +--- + ## Wrappers Best Practices ### 1. Provide Context in Parent Paths @@ -358,16 +554,20 @@ It's good practice to verify the parent path exists: ```python # Check if branch exists -branches = API.run_job('branch.listbyname', "music") +branches = API.run_job('branch.listbyname') if branches.get('Items'): - # Branch exists, proceed with post - API.wrapped(create_post( - "Content", - parent_path="/music" - )) + branch_slugs = [b.get('slug') for b in branches.get('Items', [])] + if 'music' in branch_slugs: + # Branch exists, proceed with post + API.wrapped(create_post( + "Content", + parent_path="/music" + )) + else: + print("Branch 'music' does not exist") else: - print("Branch 'music' does not exist") + print("No branches found") ``` ### 5. Handle Responses diff --git a/documentation.md b/documentation.md index 4c21b01..e383e38 100644 --- a/documentation.md +++ b/documentation.md @@ -1,155 +1,155 @@ -# Basic usage (without an .env) -## Setup -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", - 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, -) -``` -## 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. - -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 - -API.wrapped(create_post( - "This is a test post created via the create_post wrapper function.", -)) -``` - - -**_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('post.listpublic') -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` -```python -API.run_job('notification.listnotifications') -``` -### `markallasread` -```python -API.run_job('notification.markallasread') -``` -## post -### create -```python -API.run_job('post.create', { - "postText": "This is a test post created via the API wrapper.", - "parent": { - "pk": "maintrunk#maintrunk", - "sk": "maintrunk#maintrunk" - } -}) -``` -### get -### listall -### listbybranch -### listbyuserprofile -### listpublic -### listremoved -### update -## reaction -## trust -## userprofile -## vote - - -# Wrappers -## post -### `create_post` -### `update_post` -## comment -### `create_comment` - +# Basic usage (without an .env) +## Setup +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", + environment="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"), + environment="alpha", # alpha | production. + 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. + +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 + +API.wrapped(create_post( + "This is a test post created via the create_post wrapper function.", +)) +``` + + +**_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('post.listpublic') +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` +```python +API.run_job('notification.listnotifications') +``` +### `markallasread` +```python +API.run_job('notification.markallasread') +``` +## post +### create +```python +API.run_job('post.create', { + "postText": "This is a test post created via the API wrapper.", + "parent": { + "pk": "maintrunk#maintrunk", + "sk": "maintrunk#maintrunk" + } +}) +``` +### get +### listall +### listbybranch +### listbyuserprofile +### listpublic +### listremoved +### update +## reaction +## trust +## userprofile +## vote + + +# Wrappers +## post +### `create_post` +### `update_post` +## comment +### `create_comment` + # Utils \ No newline at end of file