doc: audit pass — fix env= bug, params, pagination, missing wrappers, broken examples

- 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 <environment>_handle_token template artifact
- Add CODE_BUGS.md documenting 4 code bugs found during audit
This commit is contained in:
BarnacleBoy 2026-04-18 22:58:33 +00:00
parent c8611a7b88
commit a223bcb27a
9 changed files with 552 additions and 247 deletions

101
CODE_BUGS.md Normal file
View file

@ -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).*

View file

@ -64,13 +64,13 @@ import os
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="alpha", # Options: "alpha" or "production" environment="alpha", # Options: "alpha" or "production"
debug=False debug=False
) )
# Authenticate and get user profile # Authenticate and get user profile
API.handle_token() API.handle_token()
profile = API.run_job('userprofile.get', "your-username") profile = API.run_job('userprofile.get', "your-user-slug")
print(profile) print(profile)
``` ```
@ -112,7 +112,7 @@ def saveMyToken(token_data):
API = APIClient( API = APIClient(
client_id=os.getenv("client_id"), client_id=os.getenv("client_id"),
client_secret=os.getenv("client_secret"), client_secret=os.getenv("client_secret"),
env="production", environment="production",
debug=False debug=False
) )
@ -144,7 +144,7 @@ The wrapper supports two environments:
API = APIClient( API = APIClient(
client_id="your-client-id", client_id="your-client-id",
client_secret="your-client-secret", client_secret="your-client-secret",
env="alpha", environment="alpha",
debug=False debug=False
) )
@ -152,7 +152,7 @@ API = APIClient(
API = APIClient( API = APIClient(
client_id="your-client-id", client_id="your-client-id",
client_secret="your-client-secret", client_secret="your-client-secret",
env="production", environment="production",
debug=False debug=False
) )
@ -200,8 +200,8 @@ updated_post = API.run_job('post.update', {
"newPostText": "Updated text" "newPostText": "Updated text"
}) })
# Delete a post # List removed posts
deleted = API.run_job('post.listremoved', "post-id") removed = API.run_job('post.listremoved')
``` ```
#### Comment Operations #### Comment Operations
@ -224,7 +224,7 @@ comments = API.run_job('comment.listtbypostid', "post-id")
```python ```python
# Get user profile # Get user profile
profile = API.run_job('userprofile.get', "username") profile = API.run_job('userprofile.get', "user-slug")
``` ```
#### Follow Operations #### Follow Operations
@ -232,7 +232,7 @@ profile = API.run_job('userprofile.get', "username")
```python ```python
# Follow a user # Follow a user
followed = API.run_job('follow.follow', { followed = API.run_job('follow.follow', {
"isFollowing": true, "isFollowing": True,
"parent": { "parent": {
"pk": "userprofile#username", "pk": "userprofile#username",
"sk": "userprofile#username" "sk": "userprofile#username"
@ -427,7 +427,7 @@ Enable debug mode to see all API requests and responses:
API = APIClient( API = APIClient(
client_id=os.getenv("client_id"), client_id=os.getenv("client_id"),
client_secret=os.getenv("client_secret"), client_secret=os.getenv("client_secret"),
env="production", environment="production",
debug=True # Enable debug mode debug=True # Enable debug mode
) )
@ -447,7 +447,7 @@ import os
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="alpha", environment="alpha",
debug=False debug=False
) )
@ -475,12 +475,12 @@ import os
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="production" environment="production"
) )
# Follow a user # Follow a user
follow = API.run_job('follow.follow', { follow = API.run_job('follow.follow', {
"isFollowing": true, "isFollowing": True,
"parent": { "parent": {
"pk": "userprofile#philosopher-jon", "pk": "userprofile#philosopher-jon",
"sk": "userprofile#philosopher-jon" "sk": "userprofile#philosopher-jon"
@ -504,7 +504,7 @@ import os
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="production" environment="production"
) )
API.handle_token() API.handle_token()
@ -527,7 +527,7 @@ import os
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="production" environment="production"
) )
API.handle_token() API.handle_token()

View file

@ -31,7 +31,7 @@ import os
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="alpha", # or "production" environment="alpha", # or "production"
debug=False debug=False
) )
@ -159,7 +159,8 @@ Lists all posts.
posts = API.run_job('post.listall') posts = API.run_job('post.listall')
``` ```
**Parameters:** None **Parameters:**
- `lastEvaluatedKey` (dict, optional): Pagination key for next page
**Returns:** dict - List of all posts **Returns:** dict - List of all posts
@ -173,13 +174,14 @@ print(f"Total posts: {len(all_posts.get('Items', []))}")
### post.listpublic() ### post.listpublic()
Lists public posts only. Lists public posts only. This endpoint does not require authentication.
```python ```python
public_posts = API.run_job('post.listpublic') public_posts = API.run_job('post.listpublic')
``` ```
**Parameters:** None **Parameters:**
- `lastEvaluatedKey` (dict, optional): Pagination key for next page
**Returns:** dict - List of public posts **Returns:** dict - List of public posts
@ -197,11 +199,12 @@ for post in public_posts.get('Items', []):
Lists posts in a specific branch. Lists posts in a specific branch.
```python ```python
branch_posts = API.run_job('post.listbybranch', branch_name) branch_posts = API.run_job('post.listbybranch', branch_slug)
``` ```
**Parameters:** **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 **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. Lists posts by a specific user profile.
```python ```python
user_posts = API.run_job('post.listbyuserprofile', username) user_posts = API.run_job('post.listbyuserprofile', user_slug)
``` ```
**Parameters:** **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 **Returns:** dict - List of posts by user
@ -237,17 +241,19 @@ journals = API.run_job('post.listbyuserprofile', "philosopher-jon")
Lists removed/deleted posts. Lists removed/deleted posts.
```python ```python
removed = API.run_job('post.listremoved', post_id) removed = API.run_job('post.listremoved')
``` ```
**Parameters:** **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:** **Example:**
```python ```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:** **Parameters:**
- `postId` (str): The post ID - `post_id` (str): The post ID
**Returns:** dict - List of comments **Returns:** dict - List of comments
@ -360,7 +366,7 @@ result = API.run_job('follow.follow', payload)
```python ```python
# Follow a user # Follow a user
followed = API.run_job('follow.follow', { followed = API.run_job('follow.follow', {
"isFollowing": true, "isFollowing": True,
"parent": { "parent": {
"pk": "userprofile#philosopher-jon", "pk": "userprofile#philosopher-jon",
"sk": "userprofile#philosopher-jon" "sk": "userprofile#philosopher-jon"
@ -368,9 +374,9 @@ followed = API.run_job('follow.follow', {
"followType": "userprofile", "followType": "userprofile",
"parentSlug": "philosopher-jon", "parentSlug": "philosopher-jon",
"preferences": { "preferences": {
"notification": true, "notification": True,
"emailNew": false, "emailNew": False,
"emailDigest": true "emailDigest": True
} }
}) })
``` ```
@ -796,20 +802,20 @@ For large result sets, results may include pagination:
```python ```python
def get_all_posts(): def get_all_posts():
posts = [] posts = []
paginator = None last_key = None
while True: 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', [])) posts.extend(response.get('Items', []))
has_more = 'LastEvaluatedKey' in response has_more = 'LastEvaluatedKey' in response
if not has_more: if not has_more:
break break
# Set params for next page last_key = response['LastEvaluatedKey']
params = {
'ExclusiveStartKey': response['LastEvaluatedKey']
}
return posts return posts
``` ```
@ -877,6 +883,6 @@ Implement rate limiting for production use (see main README for example).
## Next Steps ## Next Steps
- [Wrappers Guide](wrappers.md) - High-level wrappers for common tasks - [Wrappers Guide](WRAPPERS.md) - High-level wrappers for common tasks
- [Custom Requests Guide](custom_requests.md) - Making advanced API calls - [Custom Requests Guide](CUSTOM_REQUESTS.md) - Making advanced API calls
- [Troubleshooting Guide](troubleshooting.md) - Common issues and solutions - [Troubleshooting Guide](TROUBLESHOOTING.md) - Common issues and solutions

View file

@ -51,7 +51,7 @@ import os
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("CLIENT_ID"), client_id=os.getenv("CLIENT_ID"),
client_secret=os.getenv("CLIENT_SECRET"), client_secret=os.getenv("CLIENT_SECRET"),
env="alpha", environment="alpha",
debug=True debug=True
) )
@ -172,17 +172,16 @@ response = API.make_request(
) )
``` ```
### Manual token injection (advanced): ### Manual token handling (advanced):
```python ```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( response = API.make_request(
"GET", "GET",
"content", "content",
"post/some-path", "post/some-path"
headers={
"Authorization": "Bearer your-token-manually-set"
}
) )
``` ```
@ -194,30 +193,25 @@ TrustCafé responses may include pagination for large result sets:
```python ```python
def get_all_posts(limit=100): 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""" """Fetch a single page of posts"""
params = {"Limit": limit}
if start_key:
params["ExclusiveStartKey"] = start_key
response = API.make_request( response = API.make_request(
"GET", "GET",
"content", "content",
"post/listall", "post",
query_params=params, query_params=last_evaluated_key,
authenticate=True authenticate=True
) )
return response return response
all_posts = [] all_posts = []
start_key = None last_key = None
while True: while True:
page = fetch_page(start_key) page = fetch_page(last_key)
items = page.get('Items', []) items = page.get('Items', [])
all_posts.extend(items) all_posts.extend(items)
@ -227,15 +221,15 @@ def get_all_posts(limit=100):
if not has_more or len(items) == 0: if not has_more or len(items) == 0:
break break
# Set start key for next page # Pass the LastEvaluatedKey directly as query_params for next page
start_key = page['LastEvaluatedKey'] last_key = page['LastEvaluatedKey']
return all_posts return all_posts
# Usage # Usage
API.handle_token() API.handle_token()
all_posts = get_all_posts(limit=50) all_posts = get_all_posts()
print(f"Total posts: {len(all_posts)}") print(f"Total posts: {len(all_posts)}")
``` ```
@ -761,7 +755,7 @@ def conditional_update_post(post_id, updates, condition=None):
## Next Steps ## Next Steps
- [API Reference](api_reference.md) - Complete list of jobs - [API Reference](API_REFERENCE.md) - Complete list of jobs
- [Wrappers Guide](wrappers.md) - High-level wrappers for common operations - [Wrappers Guide](WRAPPERS.md) - High-level wrappers for common operations
- [Examples](../README.md#examples) - Real-world usage examples - [Examples](../README.md#examples) - Real-world usage examples
- [Troubleshooting](troubleshooting.md) - Common issues and solutions - [Troubleshooting](TROUBLESHOOTING.md) - Common issues and solutions

View file

@ -36,7 +36,10 @@ uv add trustcafeapiwrapper
```python ```python
import trustcafeapiwrapper 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 ## API Credentials Setup
@ -120,7 +123,7 @@ The TrustCafé API wrapper supports two environments:
API = APIClient( API = APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="alpha" # Alpha environment environment="alpha" # Alpha environment
) )
``` ```
@ -134,7 +137,7 @@ API = APIClient(
API = APIClient( API = APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"),
env="production" # Production environment environment="production" # Production environment
) )
``` ```
@ -142,7 +145,7 @@ API = APIClient(
```python ```python
# Initialize with alpha # 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 # Later, switch to production
API.set_environment("production") API.set_environment("production")
@ -162,7 +165,7 @@ load_dotenv()
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), client_id=os.getenv("TRUSTCAFE_CLIENT_ID"),
client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), 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 debug=True # Set to True to see request/response details
) )
@ -307,7 +310,7 @@ Always test in alpha before production:
```python ```python
# Test with alpha first # 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 # Verify token works
results = API.handle_token() results = API.handle_token()
@ -317,7 +320,7 @@ print("Alpha environment works!")
API = APIClient( API = APIClient(
client_id=os.getenv("PROD_CLIENT_ID"), client_id=os.getenv("PROD_CLIENT_ID"),
client_secret=os.getenv("PROD_CLIENT_SECRET"), client_secret=os.getenv("PROD_CLIENT_SECRET"),
env="production" environment="production"
) )
``` ```
@ -369,7 +372,7 @@ Use debug mode during development:
API = APIClient( API = APIClient(
client_id="your-id", client_id="your-id",
client_secret="your-secret", client_secret="your-secret",
env="internal", environment="alpha", # "alpha" or "production"
debug=True # Prints request/response details debug=True # Prints request/response details
) )
``` ```
@ -454,16 +457,16 @@ LOG_LEVEL=INFO
Now that you're set up: Now that you're set up:
1. **Read the API Reference**: Learn about all available jobs and wrappers 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 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 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 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 5. **Contribute**: Help complete the wrapper with additional jobs
- [Development Guide](development.md) - [Development Guide](../development.md)

View file

@ -67,6 +67,7 @@ Complete documentation for the TrustCafé API wrapper.
- Overview of wrapper system - Overview of wrapper system
- Post wrapper documentation (create_post, update_post) - Post wrapper documentation (create_post, update_post)
- Comment wrapper documentation (create_comment) - Comment wrapper documentation (create_comment)
- Interaction wrappers (follow, react, trust, votecast)
- Wrapper best practices - Wrapper best practices
- Wrapper vs Job comparison - Wrapper vs Job comparison
- Creating custom wrappers - 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. Document version updates should be listed here.
### v0.2.0 (Current) ### v0.2.0 (Current Documentation)
- Complete rewrite and enhancement of all documentation - Complete rewrite and enhancement of all documentation
- Added full API reference - Added full API reference
- Added wrappers guide - Added wrappers guide (including follow, react, trust, votecast)
- Added custom requests guide - Added custom requests guide
- Added detailed troubleshooting section - Added detailed troubleshooting section
- Enhanced development documentation - Enhanced development documentation
- Improved README.md structure and examples - Improved README.md structure and examples
### v0.1.0.13 ### v0.1.0.13 (Package Version / Initial Release)
- Initial release with basic documentation - Initial release with basic documentation
- Minimum set of jobs and wrappers - Minimum set of jobs and wrappers

View file

@ -214,7 +214,7 @@ FileNotFoundError: Token data file 'token_data_alpha.json' not found
**Solutions:** **Solutions:**
1. API `<environment>_handle_token` automatically creates file on first use 1. API `handle_token` automatically creates file on first use
2. Ensure working directory is writable 2. Ensure working directory is writable
@ -346,7 +346,7 @@ ValueError: Invalid parameter or data format
4. Use debug mode to see request: 4. Use debug mode to see request:
```python ```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 # Now you'll see the exact data being sent
API.run_job('post.create', data) API.run_job('post.create', data)
@ -389,7 +389,7 @@ FileNotFoundError: Endpoint not found
4. Use debug mode to see exact path being sent: 4. Use debug mode to see exact path being sent:
```python ```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 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( API_prod = APIClient(
client_id="prod-id", client_id="prod-id",
client_secret="prod-secret", client_secret="prod-secret",
env="production" environment="production"
) )
# Old API still has old environment # Old API still has old environment
@ -663,7 +663,7 @@ requests.exceptions.Timeout: Request timed out
API = APIClient( API = APIClient(
client_id="your-id", client_id="your-id",
client_secret="your-secret", client_secret="your-secret",
env="alpha", environment="alpha",
debug=True # Enables verbose logging debug=True # Enables verbose logging
) )
``` ```

View file

@ -7,6 +7,7 @@ This guide covers the high-level wrapper functions provided by the TrustCafé AP
- [Overview](#overview) - [Overview](#overview)
- [Post Wrappers](#post-wrappers) - [Post Wrappers](#post-wrappers)
- [Comment Wrappers](#comment-wrappers) - [Comment Wrappers](#comment-wrappers)
- [Interaction Wrappers](#interaction-wrappers)
- [Wrappers Best Practices](#wrappers-best-practices) - [Wrappers Best Practices](#wrappers-best-practices)
- [Wrapper vs Job](#wrapper-vs-job) - [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 ## Wrappers Best Practices
### 1. Provide Context in Parent Paths ### 1. Provide Context in Parent Paths
@ -358,16 +554,20 @@ It's good practice to verify the parent path exists:
```python ```python
# Check if branch exists # Check if branch exists
branches = API.run_job('branch.listbyname', "music") branches = API.run_job('branch.listbyname')
if branches.get('Items'): if branches.get('Items'):
# Branch exists, proceed with post branch_slugs = [b.get('slug') for b in branches.get('Items', [])]
API.wrapped(create_post( if 'music' in branch_slugs:
"Content", # Branch exists, proceed with post
parent_path="/music" API.wrapped(create_post(
)) "Content",
parent_path="/music"
))
else:
print("Branch 'music' does not exist")
else: else:
print("Branch 'music' does not exist") print("No branches found")
``` ```
### 5. Handle Responses ### 5. Handle Responses

View file

@ -1,155 +1,155 @@
# Basic usage (without an .env) # Basic usage (without an .env)
## Setup ## Setup
Here's a very basic example of setting up: Here's a very basic example of setting up:
```python ```python
import trustcafeapiwrapper import trustcafeapiwrapper
# Setup the API Client # Setup the API Client
API = trustcafeapiwrapper.APIClient( API = trustcafeapiwrapper.APIClient(
client_id="YOUR_CLIENT_ID", client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET", client_secret="YOUR_CLIENT_SECRET",
env="alpha" # alpha | production. environment="alpha", # alpha | production.
debug=False debug=False
) )
``` ```
or with .env or with .env
```python ```python
import trustcafeapiwrapper, os import trustcafeapiwrapper, os
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
API = APIClient( API = APIClient(
client_id=os.getenv("client_id"), client_id=os.getenv("client_id"),
client_secret=os.getenv("client_secret"), client_secret=os.getenv("client_secret"),
env="alpha", # alpha | production. environment="alpha", # alpha | production.
debug=False, debug=False,
) )
``` ```
## Handle token (easy way) ## Handle token (easy way)
```python ```python
API.handle_token() API.handle_token()
``` ```
## Handling the token yourself ## Handling the token yourself
```python ```python
def getMyToken(): def getMyToken():
# get the token from your personal store # get the token from your personal store
# initially this will be None # initially this will be None
return token_data return token_data
def saveMyToken(): def saveMyToken():
# save your token to your personal store # save your token to your personal store
return True return True
# Use the old token or None if we haven't logged in ever from here # Use the old token or None if we haven't logged in ever from here
API.set_token(getMyToken()) API.set_token(getMyToken())
# Get a new one if we don't have one or if the existing one is expired # Get a new one if we don't have one or if the existing one is expired
if not API.is_token_valid(): if not API.is_token_valid():
saveMyToken(API.sign_in()) saveMyToken(API.sign_in())
``` ```
## Use a wrapper ## Use a wrapper
Wrappers make it as simple as possible to perform an action. 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. A wrapper will come with a job and a payload ready to be passed to the `wrapped` function.
eg. Use the `create_post` wrapper eg. Use the `create_post` wrapper
```python ```python
from trustcafeapiwrapper.wrappers.post.create_post import create_post from trustcafeapiwrapper.wrappers.post.create_post import create_post
API.wrapped(create_post( API.wrapped(create_post(
"This is a test post created via the create_post wrapper function.", "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. **_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` ## 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`. 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. Use the job for getting public posts.
Now you don't need to worry about which API Now you don't need to worry about which API
```python ```python
postlist = API.run_job('post.listpublic') postlist = API.run_job('post.listpublic')
print(postlist) print(postlist)
''' '''
{ {
"Items": [ "Items": [
{ {
... ...
''' '''
``` ```
**_NOTE:_** There are aren't the complete set yet created here. **_NOTE:_** There are aren't the complete set yet created here.
## Custom Request ## 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. 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. Use the `make_request` function to make a request from the content API to get posts in the music branch.
```python ```python
postlist = API.make_request("GET", "content", f"post/ref-subwiki/music") postlist = API.make_request("GET", "content", f"post/ref-subwiki/music")
print(postlist) print(postlist)
''' '''
{ {
"Items": [ "Items": [
{ {
... ...
''' '''
``` ```
# Jobs # Jobs
## block ## block
## branch ## branch
### `get` ### `get`
### `listbyname` ### `listbyname`
## change ## change
## comment ## comment
### `create` ### `create`
### `listbypostid` ### `listbypostid`
## feed ## feed
## mute ## mute
## notifcation ## notifcation
### `listnotifications` ### `listnotifications`
```python ```python
API.run_job('notification.listnotifications') API.run_job('notification.listnotifications')
``` ```
### `markallasread` ### `markallasread`
```python ```python
API.run_job('notification.markallasread') API.run_job('notification.markallasread')
``` ```
## post ## post
### create ### create
```python ```python
API.run_job('post.create', { API.run_job('post.create', {
"postText": "This is a test post created via the API wrapper.", "postText": "This is a test post created via the API wrapper.",
"parent": { "parent": {
"pk": "maintrunk#maintrunk", "pk": "maintrunk#maintrunk",
"sk": "maintrunk#maintrunk" "sk": "maintrunk#maintrunk"
} }
}) })
``` ```
### get ### get
### listall ### listall
### listbybranch ### listbybranch
### listbyuserprofile ### listbyuserprofile
### listpublic ### listpublic
### listremoved ### listremoved
### update ### update
## reaction ## reaction
## trust ## trust
## userprofile ## userprofile
## vote ## vote
# Wrappers # Wrappers
## post ## post
### `create_post` ### `create_post`
### `update_post` ### `update_post`
## comment ## comment
### `create_comment` ### `create_comment`
# Utils # Utils