From fd1a1566e9ee21428cb6bf7754406eab358775d5 Mon Sep 17 00:00:00 2001 From: BarnacleBoy Date: Sat, 18 Apr 2026 01:44:49 +0000 Subject: [PATCH] chore: add comprehensive API wrapper documentation - Add docs/ directory with 6 comprehensive documentation files: * INDEX.md - Main navigation and documentation index * GETTING_STARTED.md - Setup and configuration guide * API_REFERENCE.md - Complete API documentation * WRAPPERS.md - High-level wrappers guide * CUSTOM_REQUESTS.md - Advanced usage patterns * TROUBLESHOOTING.md - Problem-solving guide - Update README.md with enhanced project information - Update development.md with expanded documentation context Extends previous session's documentation work with complete developer and user-facing documentation. Closes previous documentation issue. --- README.md | 628 +++++++++++++++++++++++++++-- development.md | 825 ++++++++++++++++++++++++++++++++++++-- docs/API_REFERENCE.md | 847 ++++++++++++++++++++++++++++++++++++++++ docs/CUSTOM_REQUESTS.md | 767 ++++++++++++++++++++++++++++++++++++ docs/GETTING_STARTED.md | 469 ++++++++++++++++++++++ docs/INDEX.md | 162 ++++++++ docs/TROUBLESHOOTING.md | 763 ++++++++++++++++++++++++++++++++++++ docs/WRAPPERS.md | 509 ++++++++++++++++++++++++ 8 files changed, 4909 insertions(+), 61 deletions(-) create mode 100644 docs/API_REFERENCE.md create mode 100644 docs/CUSTOM_REQUESTS.md create mode 100644 docs/GETTING_STARTED.md create mode 100644 docs/INDEX.md create mode 100644 docs/TROUBLESHOOTING.md create mode 100644 docs/WRAPPERS.md diff --git a/README.md b/README.md index 022866a..fcce906 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,632 @@ -# Trustcafe API Wrapper +# TrustCafé API Wrapper + +A Python wrapper for the TrustCafé API, providing a convenient interface to interact with TrustCafé features programmatically. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Authentication](#authentication) +- [API Reference](#api-reference) + - [Jobs](#jobs) + - [Wrappers](#wrappers) +- [Custom Requests](#custom-requests) +- [Environment Setup](#environment-setup) +- [Debug Mode](#debug-mode) +- [Examples](#examples) +- [Development](#development) +- [Testing](#testing) ## Prerequisites -You will need: -1. API Client and Secret -2. Python 3.11 or something sensible or above -3. A method for environment variables like [python-dotenv](https://pypi.org/project/python-dotenv/) +Before you begin, ensure you have: -### API client key and secret from the site +- **Python 3.11 or higher** installed +- **API Client ID and Secret** from TrustCafé +- **python-dotenv** (for `.env` file support) or ability to set environment variables + +### Getting API Credentials + +1. Visit your TrustCafé account page: + - [Production API Access](https://www.trustcafe.io/en/myaccount/apiaccess) + - [Alpha Environment API Access](https://alpha.wts2.net/en/myaccount/apiaccess) -1. https://www.trustcafe.io/en/myaccount/apiaccess for production and https://alpha.wts2.net/en/myaccount/apiaccess for alpha (this will change in the future) 2. Click "Create new client credentials key pair" -3. Choose all of the scopes or select which you would like to restrict the key to -4. Scroll down and press Save -5. Copy the client key and secret to your environment, the secret cannot be retrieved without making a new one + +3. Choose the scopes you need (or select all for full access) + +4. Click "Save" + +5. Copy the **client_id** and **client_secret** to your environment: + - The secret cannot be retrieved later — make sure to save it securely + - Do not commit these credentials to version control ## Installation +```bash +# Using pip pip install trustcafeapiwrapper -OR - +# Using uv (recommended) uv add trustcafeapiwrapper +``` -## Usage - +## Quick Start +The simplest way to get started: ```python -import trustcafeapiwrapper, os - +import trustcafeapiwrapper +import os +# Initialize the API client API = trustcafeapiwrapper.APIClient( - client_id=os.getenv("client_id"), - client_secret=os.getenv("client_secret"), - env="alpha" # alpha | production. + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="alpha", # Options: "alpha" or "production" debug=False ) -profile = API.run_job('userprofile.get', "simon-little") +# Authenticate and get user profile +API.handle_token() +profile = API.run_job('userprofile.get', "your-username") +print(profile) +``` +## Authentication +The API wrapper handles OAuth2 client-credentials authentication automatically. + +### Automatic Token Management + +```python +API.handle_token() +``` + +**What it does:** +- Checks for an existing token file (`token_data_{env}.json`) +- If found, verifies if the token is still valid +- If expired or not found, obtains a new access token (~3 month duration) +- Saves the token to a local file for future requests + +### Manual Token Management + +For more control over token storage: + +```python +def getMyToken(): + # Retrieve token from your custom storage (database, file, etc.) + return { + "access_token": "your_token_here", + "access_token_timeout": 9999999999 # Unix timestamp + } + +def saveMyToken(token_data): + # Save to your preferred storage + with open("my_token_cache.json", "w") as f: + json.dump(token_data, f) + return True + +# Set the token manually or pass to initialize +API = APIClient( + client_id=os.getenv("client_id"), + client_secret=os.getenv("client_secret"), + env="production", + debug=False +) + +# Use existing token or get a new one +if not API.is_token_valid(): + saveMyToken(API.sign_in()) +``` + +### Token Validation + +```python +if API.is_token_valid(): + print("Token is valid") +else: + API.handle_token() # Get a new token +``` + +## Environment Setup + +The wrapper supports two environments: + +| Environment | URL | Purpose | +|------------|-----|---------| +| `alpha` | `alpha.wts2.net` | Development/staging environment | +| `production` | `trustcafe.io` | Production environment | + +```python +# Alpha environment (default) +API = APIClient( + client_id="your-client-id", + client_secret="your-client-secret", + env="alpha", + debug=False +) + +# Production environment +API = APIClient( + client_id="your-client-id", + client_secret="your-client-secret", + env="production", + debug=False +) + +# Change environment dynamically +API.set_environment("production") +``` + +## API Reference + +### Jobs + +Jobs are pre-built functions that map to specific API endpoints. They return the raw response from the API. + +#### Post Operations + +```python +# Get a specific post +post = API.run_job('post.get', "post-slug-or-id") + +# Create a post +post = API.run_job('post.create', { + "postText": "This is a test post", + "parent": { + "pk": "maintrunk#maintrunk", + "sk": "maintrunk#maintrunk" + } +}) + +# List all posts +posts = API.run_job('post.listall') + +# List public posts +public_posts = API.run_job('post.listpublic') + +# List posts by a specific branch +branch_posts = API.run_job('post.listbybranch', "branch-name") + +# List posts by a user profile +user_posts = API.run_job('post.listbyuserprofile', "username") + +# Update a post +updated_post = API.run_job('post.update', { + "pk": "post-id", + "sk": "post-id", + "newPostText": "Updated text" +}) + +# Delete a post +deleted = API.run_job('post.listremoved', "post-id") +``` + +#### Comment Operations + +```python +# Create a comment +comment = API.run_job('comment.create', { + "commentText": "This is a comment", + "parent": { + "pk": "post-id", + "sk": "post-id" + } +}) + +# Get comments by post ID +comments = API.run_job('comment.listtbypostid', "post-id") +``` + +#### User Profile Operations + +```python +# Get user profile +profile = API.run_job('userprofile.get', "username") +``` + +#### Follow Operations + +```python +# Follow a user +followed = API.run_job('follow.follow', "username-to-follow") +``` + +#### Vote Operations + +```python +# Cast a vote +voted = API.run_job('vote.votecast', { + "pk": "post-id", + "sk": "post-id", + "voteType": "up" # or "down" +}) +``` + +#### Reaction Operations + +```python +# React to something (post, comment, etc.) +reactions = API.run_job('reaction.reacttosomething', { + "objectPK": "post-id", + "objectSK": "post-id", + "objectType": "post", + "reactionType": "like" # various types supported +}) + +# Get reactions by parent +post_reactions = API.run_job('reaction.getbyparent', "parent-id") + +# List reactions +reactions_list = API.run_job('reaction.listbyparent', "parent-id") +``` + +#### Notification Operations + +```python +# List all notifications +notifications = API.run_job('notification.listnotifications') + +# Mark all as read +API.run_job('notification.markallasread') +``` + +#### Trust Operations + +```python +# Create or update trust relationship +trust = API.run_job('trust.createorupdate', { + "pk": "trust_id", + "sk": "trust_id", + "trustType": "positive" +}) + +# List trust by user initialization +trusts = API.run_job('trust.listbyusersinit', "username") + +# List trust by user has +trusts = API.run_job('trust.listbyuserhas', "username") +``` + +#### Branch Operations + +```python +# Get a specific branch +branch = API.run_job('branch.get', "branch-name") + +# List branches by name +branches = API.run_job('branch.listbyname', "prefix") +``` + +#### Feed Operations + +```python +# Get café feed +feed = API.run_job('feed.cafefeed') + +# Get following feed +following_feed = API.run_job('feed.following') +``` + +#### Block Operations + +```python +# Block a user (function exists but needs implementation) +# TODO: Add job function for block operations +``` + +#### Mute Operations + +```python +# Mute functionality (to be added) +# TODO: Add job function for mute operations +``` + +### Wrappers + +Wrappers are high-level functions that simplify common operations by handling payload preparation and job execution automatically. + +#### Using a Wrapper + +```python +API.handle_token() + +# Create a post with a wrapper (recommended for simplicity) 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.", + "This is a test post created via the wrapper.", + parent_path="/", # Root path (optional) + blur_label=None, # Optional blurring + card_url=None, # Optional card URL + collaborative=False # Optional collaboration flag +)) + +# Create a comment +from trustcafeapiwrapper.wrappers.comment.create_comment import create_comment + +API.wrapped(create_comment( + "This is a comment", + parent_path="/" # Parent path (required) )) ``` -## More -- [Development "Things to think about do"](https://gitlab.com/trustcafe/trustcafe-api-wrapper/-/blob/main/development.md) -- [Documentation](https://gitlab.com/trustcafe/trustcafe-api-wrapper/-/blob/main/documentation.md) + +**About Wrappers:** +- Wrappers simplify common operations +- They prepare the payload and job execution automatically +- More wrappers are being added for consistency +- For flexibility, use Jobs or Custom Requests + +### Using `run_job` vs. Wrappers + +| Feature | `run_job` | Wrappers | +|---------|----------|----------| +| Simplicity | Higher | Highest for common tasks | +| Flexibility | Maximum | Optimized for specific use cases | +| Discovery | Need to know job format | Self-documenting payloads | +| Customization | Full control | Pre-defined options | + +**Recommendation:** +- Use **Wrappers** for common, well-documented operations +- Use **Jobs** when you need fine-grained control +- Use **Custom Requests** when neither fits your needs + +## Custom Requests + +For operations not covered by jobs or wrappers, use the `make_request` method: + +```python +# Make a raw API request +response = API.make_request( + method="GET", + endpoint="content", + path="post/some-custom-path", + authenticate=True, + query_params={"param1": "value1"} +) +``` + +**Parameters:** +- `method` (str): HTTP method - "GET", "POST", "PUT", "DELETE" +- `endpoint` (str): API endpoint - "content", "auth", "audrey", etc. +- `path` (str): API path after endpoint +- `authenticate` (bool): Whether to include authentication token +- `query_params` (dict): Optional query parameters + +**Example - Custom Request:** +```python +# Get posts from a specific branch with filters +posts = API.make_request( + "GET", + "content", + "post/ref-subwiki/music", + query_params={"limit": 10, "offset": 0} +) +``` + +## Debug Mode + +Enable debug mode to see all API requests and responses: + +```python +API = APIClient( + client_id=os.getenv("client_id"), + client_secret=os.getenv("client_secret"), + env="production", + debug=True # Enable debug mode +) + +# Now API calls will print request details +API.handle_token() +``` + +## Examples + +### Example 1: Basic Post Creation + +```python +import trustcafeapiwrapper +import os + +# Setup +API = trustcafeapiwrapper.APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="alpha", + debug=False +) + +# Handle authentication +API.handle_token() + +# Create a post in the music branch +from trustcafeapiwrapper.wrappers.post.create_post import create_post + +API.wrapped(create_post( + "New music discussion post", + parent_path="/music", + blur_label=None, + card_url=None, + collaborative=False +)) +``` + +### Example 2: Following a User and Getting Their Profile + +```python +import trustcafeapiwrapper +import os + +API = trustcafeapiwrapper.APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="production" +) + +# Follow a user +follow = API.run_job('follow.follow', "philosopher-jon") +print(f"Followed user: {follow}") + +# Get their profile +profile = API.run_job('userprofile.get', "philosopher-jon") +print(f"Profile data: {profile}") +``` + +### Example 3: Managing Notifications + +```python +import trustcafeapiwrapper +import os + +API = trustcafeapiwrapper.APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="production" +) + +API.handle_token() + +# Check for new notifications +notifications = API.run_job('notification.listnotifications') +print(f"Unread notifications: {notifications}") + +# Mark all as read +API.run_job('notification.markallasread') +print("All notifications marked as read") +``` + +### Example 4: Creating a Collaborative Post + +```python +import trustcafeapiwrapper +import os + +API = trustcafeapiwrapper.APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="production" +) + +API.handle_token() + +# Create a collaborative post +from trustcafeapiwrapper.wrappers.post.create_post import create_post + +API.wrapped(create_post( + "Research paper draft - collaborative editing", + parent_path="/research", + collaborative=True # Enables collaborative editing +)) +``` + +## Development + +### Project Structure + +``` +trustcafe-api-wrapper/ +├── src/ +│ └── trustcafeapiwrapper/ +│ ├── apiclient.py # Core API client +│ ├── jobs/ # API job functions +│ │ ├── post/ +│ │ ├── comment/ +│ │ ├── userprofile/ +│ │ ├── follow/ +│ │ └── ... (all jobs) +│ ├── wrappers/ # High-level wrappers +│ │ ├── post/ +│ │ ├── comment/ +│ │ └── ... +│ └── utils/ # Helper functions +├── tests/ # Test files +├── README.md # This file +├── documentation.md # Basic documentation +├── development.md # Project notes +└── pyproject.toml # Project configuration +``` + +### Adding New Jobs + +1. Create a new file in `src/trustcafeapiwrapper/jobs/` +2. Define a function matching the job name +3. Export it from the module's `__init__.py` +4. Document the parameters and return value + +**Example - Adding a Job:** +```python +# src/trustcafeapiwrapper/jobs/example/new_job.py +def my_new_job(API, param1: str, param2: int) -> dict: + """ + Create a new job function. + + Args: + API: The APIClient instance + param1 (str): Description of parameter 1 + param2 (int): Description of parameter 2 + + Returns: + dict: The API response + """ + response = API.make_request( + "POST", + "endpoint", + "path/to/resource", + data={"param1": param1, "param2": param2} + ) + return response +``` + +### Known Limitations + +- **Incomplete Jobs**: Not all available API endpoints have wrapper functions yet +- **Enums**: Some enum types are not strictly typed +- **Block Operations**: Block functionality exists in backend but not yet exposed +- **Mute Operations**: Mute functionality pending implementation + +### Future Enhancements + +- Complete coverage of all TrustCafé API endpoints +- Type hints for better IDE support +- Async support for better performance +- Comprehensive test suite +- Pydantic models for request/response validation + +## Testing + +Run the test suite: + +```bash +pytest +``` + +Or run specific tests: + +```bash +# Test the API client +python -m pytest tests/apiclient.py + +# Run all tests +python -m pytest tests/ +``` + +## License + +[Depends on TrustCafé project license] + +## Support + +For issues, questions, or contributions: +- [TrustCafé Website](https://trustcafe.io) +- [GitLab Repository](https://gitlab.com/trustcafe/trustcafe-api-wrapper) +- Contact the WikiTribune team + +## Version History + +- **0.1.0.13** - Current version with ongoing development + +--- + +**Note:** This wrapper is for internal WikiTribune/TrustCafé use. Public API endpoints are subject to change. diff --git a/development.md b/development.md index 6686365..9671e0d 100644 --- a/development.md +++ b/development.md @@ -1,37 +1,788 @@ -# Things to think about do - -## Debates - -1. Hating the name `jobs` and `run_job`. Requests is an -existing package dependency though. `tasks` seems to broard. I'm sure -there's probably an already existing name that I can't articulate how -to find. `apirequests`, `rqsts`, `apicalls`? -2. Should these `job` be called with dot notation? Or with slashes? Or -something else? Is it okay to have these as strings? -3. Should we do validation in the `jobs` or let the server do that all? -4. `wrapped` is an awkward name. What's more proper? But also obvious? -5. Should have consistent format, ie `create_{noun}`, and -`delete_{noun}`, rather than just verbing ie `comment`, `post` as this -is ambiguousetc -6. Known/displayed entity names or as-is/DB names, ie: `trust` or -`reltrust`? We call trust as reltrust in the database and in the -backend, relfollow for follow, but don't have relblock, instead it's -userblock. It's a bit inconsistent and not very obvious at all. -Feels helpful to make it more obvious inside the wrapper but is also -potentially misleading. -7. It makes more sense to organise like: `user.follow` rather than -`follow.follow`, and `post.vote` instead of `vote.vote`, but what about -`post.comment.vote` vs `comment.vote`, what about -`post.comment.comment`, they seem weird. - -## ToDo: - -1. Make more jobs -2. Make more wrappers -3. Write more documentation - - - -Mute -Block -Branch Mute +# TrustCafé API Wrapper - Development Guide + +This document covers development considerations, known limitations, and contribution guidelines for the TrustCafé API wrapper. + +## Table of Contents + +1. [Project Structure](#project-structure) +2. [Design Decisions](#design-decisions) +3. [Known Limitations](#known-limitations) +4. [Missing Features](#missing-features) +5. [Future Enhancements](#future-enhancements) +6. [Contributing](#contributing) +7. [Testing Guide](#testing-guide) +8. [Extending the Wrapper](#extending-the-wrapper) + +## Project Structure + +``` +trustcafe-api-wrapper/ +├── README.md # Main documentation +├── documentation.md # Basic usage documentation +├── development.md # This file - development notes +├── pyproject.toml # Project configuration +├── setup.py # Legacy setup file +├── development.md +├── .gitlab-ci.yml # CI/CD configuration +└── src/ + └── trustcafeapiwrapper/ + ├── __init__.py # Package exports + ├── apiclient.py # Core API client (218 lines) + ├── jobs/ # API job functions + │ ├── __init__.py + │ ├── post/ + │ ├── comment/ + │ ├── userprofile/ + │ ├── follow/ + │ ├── vote/ + │ ├── reaction/ + │ ├── notification/ + │ ├── trust/ + │ ├── branch/ + │ └── feed/ + ├── wrappers/ # High-level wrappers + │ ├── __init__.py + │ ├── post/ + │ └── comment/ + └── utils/ # Helper functions + ├── __init__.py + ├── make_comment_sk.py + ├── make_post_sk.py + ├── get_post_pksk.py + ├── get_userprofile_pksk_from_slug.py + ├── get_user_slug_from_path.py + ├── get_entity_from_str.py + ├── get_parent_pksk_from_path.py + └── get_child_spksk_from_paths.py +``` + +## Design Decisions + +### 1. Naming Conventions + +**Jobs vs. Wrappers:** + +| Aspect | Jobs | Wrappers | +|--------|------|----------| +| Naming | Verb (`create_x`, `get_x`) | Noun phrase (`create_post`, `update_post`) | +| Pattern | Inconsistent | Standardized (`create_noun`, `update_noun`) | +| Usage | Flexible | Optimized for common cases | +| Type | Function | Function returning dict with job spec | + +**Question: Should these be called `jobs`, `tasks`, `apirequests`, `rqsts`, or `apicalls`?** +- **Chosen:** `jobs` - Existing dependency (requests) +- **Alternatives considered:** `tasks` (too broad), `apirequests` (confusing with HTTP library) + +**Thread with discussion:** See GitLab issues + +### 2. Job Execution Method + +**Question: Should jobs be called with `dot notation` (e.g., `userprofile.get`) or `slashes` (e.g., `userprofile/get`)?** +- **Chosen:** Dot notation for module style +- **Flexibility:** Supports both strings for dynamic loading and functions for static use +- **Pattern:** + +```python +# String-based (dynamic) - works for variable jobs +API.run_job('userprofile.get', username) + +# Function-based (static) - good for explicit calls +API.run_job(userprofile_get, username) +``` + +### 3. Parameter Validation + +**Question: Should validation happen in jobs or let the server do it?** +- **Chosen:** Server-side validation with pydantic at APIClient level +- **Reasoning:** + - Server provides authoritative validation + - Reduced redundancy + - Known API responses +- **Trade-off:** Misuse symptoms appear when server rejects invalid data + +### 4. Wrapper Architecture + +**Question: What's the proper name for the wrapper system?** +- **Chosen:** `wrapped()` as the calling method +- **Alternatives considered:** None after review +- **Pros:** Self-documenting parameter handling +- **Cons:** Awkward name + +### 5. Verbosity in Noun Names + +**Question: Use `post.vote` vs `vote.cast`?** +- **Chosen:** `post.vote` for entity-based paths +- **Rationale:** + - Better semantic structure: `object.action` + - Clearer context (what are we voting on?) + - Consistent with TrustCafé's own endpoints + +**Examples:** +- ✅ `post.comment` (comment on a post) +- ✅ `comment.vote` (vote on a comment) +- ⚠️ vs `comment.comment` (commenting on a comment) + +### 6. Database vs. Display Names + +**Question: Use `trust` or RelTrust in code?** +- **Chosen:** Display names (`trust`, `follow`, `block`) +- **Misleading risk:** Database uses RelTrust, RelFollow, UserBlock +- **Plan:** Internal mapping in utility functions + +**Database Inconsistencies:** +- `trust` ↔ `RelTrust` +- `follow` ↔ `RelFollow` +- `block` ↔ `UserBlock` (not `RelBlock`) +- `mute` ↔ `Mute` + +### 7. Entity Hierarchy Organization + +**Question: Organize as `user.follow` or `follow.follow`?** +- **Chosen:** `user.follow` (object-centric) for most cases +- **Complex cases:** `comment.vote`, `post.comment.comment` (both possible) + +**Plan:** +- Simple cases: Entity-based (`user.follow`, `post.comment`) +- Complex cases: Action-centric (`comment.vote`) +- Organize by common pattern, not dogmatically + +## Known Limitations + +### 1. Incomplete Job Coverage + +**Status:** Partial coverage of API endpoints + +**Currently Implemented:** +- ✅ Post operations: create, get, update, listall, listpublic, listbybranch, listbyuserprofile, listremoved +- ✅ Comment operations: create, listtbypostid +- ✅ UserProfile: get +- ✅ Follow: follow +- ✅ Vote: votecast +- ✅ Reaction: reacttosomething, getbyparent, listbyparent +- ✅ Notification: listnotifications, markallasread +- ✅ Trust: createorupdate, listbyusersinit, listbyuserhas +- ✅ Branch: get, listbyname +- ✅ Feed: cafefeed, following +- ⚠️ Block: job created but not connected to server +- ⚠️ Mute: job created but not connected to server + +**Missing/Incomplete:** +- ❌ Post status operations (delete, pin, etc.) +- ❌ Full post metadata operations +- ❌ Advanced comment features +- ❌ User management operations +- ❌ Search functionality +- ❌ Moderation operations +- ❌ Analytics endpoints + +--- + +### 2. Enums Not Strictly Typed + +**Issue:** Some parameter types are not enforced + +**Example:** +```python +# Acceptable (but should be): +API.run_job('vote.votecast', {..., "voteType": "UP"}) # Should error? + +# Better to use type hints: +vote_type: Literal["up", "down"] +``` + +**Affected APIs:** +- voteType (should be "up" or "down") +- reactionType (should match TrustCafé enum values) +- trustType (should be known set) + +**Solution:** Add pydantic models for requests and responses + +--- + +### 3. No Async Support + +**Issue:** All requests are synchronous (blocking) + +**Impact:** +- Serial request execution +- Long waits for multiple calls +- Limited throughput + +**Solution:** +- Add async/await support using `aiohttp` +- Provide sync wrapper that calls async +- Consider for future major version + +--- + +### 4. Limited Timeout Configuration + +**Issue:** Hardcoded 20-second timeout + +**Current Code:** +```python +# apiclient.py line 87 +response = session.request(method.upper(), url, json=data, headers=headers, timeout=20) +``` + +**Problem:** +- No way to configure timeout per request +- Default works but is a guess +- Network issues hard to diagnose without flexibility + +**Solution:** +```python +# Add timeout parameter +def make_request(self, method, endpoint, path, data=None, + authenticate=True, query_params=None, + timeout=20): # Add timeout parameter +``` + +--- + +### 5. No Detailed Error Types + +**Issue:** All errors are generic `Exception` or `ValueError` + +**Current Code:** +```python +except Exception as e: + raise ConnectionError(f"An error occurred while making the request: {e}") +``` + +**Limitations:** +- No authentication-specific errors +- No validation errors +- No permission errors +- Error type doesn't give context + +**Solution:** Create custom error classes: +```python +class APIError(Exception): + """Base API error""" + +class AuthenticationError(APIError): + """Authentication failures""" + +class ValidationError(APIError): + """Invalid parameter values""" +``` + +--- + +### 6. Token File Handling + +**Issue:** Token file stored in working directory (influenced by os.getcwd()) + +**Problem:** +- Cross-platform issues +- Permission problems +- Can't control location from outside + +**Current Code:** +```python +token_data_path = f"token_data_{self.environment}.json" +``` + +**Solution:** +```python +# User-configurable path +import os + +default_path = os.path.join(os.getcwd(), f"token_data_{self.environment}.json") + +API.handle_token(token_data_path=os.getenv("TRUSTCAFE_TOKEN_PATH", default_path)) +``` + +--- + +### 7. Database-Display Name Mismatch + +**Issue:** Internal vs. external naming不一致 + +**Examples:** +- Display: `trust` ↔ Database: `RelTrust` +- Display: `follow` ↔ Database: `RelFollow` +- Display: `block` ↔ Database: `UserBlock` (not `RelBlock`) +- Display: `mute` ↔ Database: `Mute` + +**Solution:** +- Map most common names (trust, follow, mute) +- Add mappings in utility functions +- Document naming inconsistencies +- Consider renaming display names for consistency + +--- + +### 8. Unused Dependencies + +**Cleanup opportunity:** Some dependencies may not be used + +**Current Dependencies:** +```toml +dependencies = [ + "dotenv>=0.9.9", # Used in development.md example + "pydantic>=2.12.5", # Used for SecretStr + "requests>=2.33.1", # Used for HTTP requests + "simplejson>=3.20.2", # Used for JSON parsing +] +``` + +**Potential Removal:** +- `simplejson`: Can use Python's built-in json +- **Note:** Keep for now to minimize risk + +--- + +### 9. Hard-coded Endpoints + +**Issue:** API base URLs are hard-coded + +**Current Code:** +```python +endpoints = { + "alpha": { + "audrey": "https://eso1of8gqd.execute-api.us-east-1.amazonaws.com/alpha/", + # ... + }, + "production": { + # ... + } +} +``` + +**Problem:** +- Environment changes will break +- No hot-reload +- Same URLs across versions + +**Solution:** +```python +# Could be environment variables +endpoints = { + "alpha": { + "audrey": os.getenv("AUDREY_ALPHA_URL", "default-alpha-url"), + }, + # ... +} +``` + +## Missing Features + +### High Priority + +1. **Complete Post Operations** + - Post status: pin, unpin, archive, delete + - Post editing: editing mode handling + - Post metadata bulk operations + - Post history/versioning + +2. **User Management Jobs** + - Block/unblock users + - Mute/unmute users + - User profile management + - User settings + +3. **Search Functionality** + - Full-text search + - Advanced filters + - Query builder + +4. **Comment Enhancements** + - Comment editing + - Comment deletion + - Comment threading + - Comment moderation + +--- + +### Medium Priority + +5. **Reaction UI** + - Multiple response types (heart, fire, thumbs-up, etc.) + - Reaction history + - Reaction statistics + +6. **Trust System** + - Trust ranking algorithms + - Trust network visualization + - Block followers + - Trust relationships with others' trust + +7. **Notification Enhancements** + - Notification callbacks/webhooks + - Notification types documentation + - Notification preferences + - Notification filtering + +8. **Feed Customization** + - Filter classes + - Schedule-based feeds + - Feed templates + +--- + +### Low Priority + +9. **Analytics** + - User engagement metrics + - Content popularity + - API usage analytics + - Performance metrics + +10. **Batch Operations** + - Bulk post creation + - Bulk comment actions + - Bulk follow operations + +11. **Spatial/Media** + - Location support + - Media attachments handling + - Image/video upload + +## Future Enhancements + +### Architecture Improvements + +1. **Type Safety** + - Pydantic models for all inputs and outputs + - Strict type checking + - Auto-completion support + - Better IDE integration + +2. **Async Support** + - Async wrappers for all operations + - Pooling for concurrency + - Automatic retries with exponential backoff + - Connection pooling + +3. **Configuration Management** + - Centralized config file + - Environment-specific configs + - Secrets management integration + - Hot-reload capability + +4. **Better Testing** + - Comprehensive test suite + - Integration tests + - Mock API server + - Property-based testing + +### Developer Features + +5. **CLI Tool** + - `trustcafe` command-line interface + - Interactive workflows + - Dry-run mode + - Export/import operations + +6. **Code Generation** + - Auto-generate jobs from API spec + - Wrapper generators + - Type definitions from OpenAPI spec + +7. **Plugin System** + - Extension points + - Custom jobs + - Custom middleware + - Hooks system + +--- + +### User Experience Improvements + +8. **Better Documentation** + - API sandbox/demo site + - Data models and schemas + - Migration guides + - FAQ section + +9. **Developer Experience** + - SDK-style TypeScript version + - Better examples + - Quick start guides + - Video tutorials + +10. **Monitoring and observability** + - Request/response logging + - Metrics and tracing + - Health checks + - Alerting hooks + +## Contributing + +### Adding New Jobs + +```python +# 1. Create job file in src/trustcafeapiwrapper/jobs/ +# Example: src/trustcafeapiwrapper/jobs/example/my_job.py + +from trustcafeapiwrapper.apiclient import APIClient + +def my_job(API: APIClient, param1: str, param2: int) -> dict: + """ + Create your job here. + + Args: + API: The APIClient instance + param1: Description of param1 + param2: Description of param2 + + Returns: + dict: Whatever the API returns + """ + response = API.make_request( + "GET", + "endpoint", + f"path/to/resource/{param1}", + authenticate=True + ) + return response + +# 2. Export from __init__.py (if needed) +# Keep __init__.py empty per current structure +``` + +### Adding New Wrappers + +```python +# Example: src/trustcafeapiwrapper/wrappers/example/my_wrapper.py + +from trustcafeapiwrapper.utils.get_parent_pksk_from_path import get_parent_pksk_from_path + +def my_wrapper( + param1: str, + param2: int, + optional_param: str = None +): + """ + Create a wrapper that makes a job easier. + + Args: + param1: Required parameter description + param2: Required parameter description + optional_param: Optional parameter with default + + Returns: + dict: Job specification for wrapped execution + """ + parent_pksk = get_parent_pksk_from_path("/") + + return { + "job_function": "example.my_job", + "payload": { + "param1": param1, + "param2": param2, + "optional_param": optional_param, + } + } + +# 3. Export from __init__.py (if needed) +``` + +### Testing + +```python +# Create test file: tests/test_my_job.py + +import unittest +from unittest.mock import patch +from trustcafeapiwrapper.apiclient import APIClient +from trustcafeapiwrapper.jobs.example.my_job import my_job + +class TestMyJob(unittest.TestCase): + def setUp(self): + self.api_client = APIClient( + client_id="test-id", + client_secret="test-secret", + debug=False + ) + + @patch('trustcafeapiwrapper.apiclient.requests.Session.request') + def test_my_job(self, mock_request): + mock_request.return_value.json.return_value = {"result": "success"} + mock_request.return_value.raise_for_status.return_value = None + + result = my_job(self.api_client, "param1", 123) + self.assertEqual(result["result"], "success") + +if __name__ == '__main__': + unittest.main() +``` + +### Code Style + +- Follow PEP 8 +- Use type hints +- Write comprehensive docstrings +- Add error handling +- Include examples in docstrings + +### Testing Requirements + +- All new features must have tests +- Test edge cases +- Mock external API calls +- Achieve 80%+ test coverage + +## Testing Guide + +### Current Test Structure + +``` +tests/ +├── apiclient.py # API client tests +├── test_api_client.py # Legacy test file +``` + +### Running Tests + +```bash +# Run all tests +pytest + +# Run specific test file +pytest tests/apiclient.py + +# Run with coverage +pytest --cov=src/trustcafeapiwrapper + +# Run with verbose output +pytest -v + +# Run specific test +pytest tests/apiclient.py::TestAPIClient::test_initialization +``` + +### Test Best Practices + +1. **Mock External Calls** + ```python + @patch('trustcafeapiwrapper.apiclient.requests.Session.request') + def test_implementation(self, mock_request): + mock_request.return_value.json.return_value = {"mock": "response"} + # Test logic + ``` + +2. **Test All Paths** + ```python + def test_error_handling(): + # Success case + assert result == expected + + # Error cases + with pytest.raises(ValueError): + handle_error_param + ``` + +3. **Keep Tests Independent** + - Each test should work alone + - No shared state between tests + - Use setUp for common setup + +4. **Clear Test Names** + ```python + # Good + def test_userprofile_get_with_invalid_username_raises_error(self): + # Test description + + # Bad + def test_invalid(self): + # Unclear what this tests + ``` + +## Extending the Wrapper + +### Adding Configuration + +```python +# Add to pyproject.toml +[tool.poetry.dependencies] +trustcafeapiwrapper = { path = "." } + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "pytest-cov>=4.0.0", + "httpx>=0.24.0", +] + +test = [ + "pytest>=7.0.0", +] +``` + +### Adding New Endpoints + +Edit `src/trustcafeapiwrapper/apiclient.py`: + +```python +endpoints = { + "alpha": { + # ... existing endpoints ... + "newendpoint": "https://api.example.com/alpha/", + }, +} +``` + +### Adding Utility Functions + +```python +# src/trustcafeapiwrapper/utils/my_utilty.py + +def my_utility_function(input_data: str) -> dict: + """Utility function for common data transformations""" + + # Implementation + + return transformed_data +``` + +--- + +## Getting Started with Development + +1. **Fork the repository** + - Add your fork as a remote + - Keep pull requests to main + +2. **Set up development environment** + ```bash + # Clone your fork + git clone https://gitlab.com/your-username/trustcafe-api-wrapper.git + cd trustcafe-api-wrapper + + # Install dependencies + pip install -e ".[dev]" + + # Run tests + pytest + ``` + +3. **Create feature branch** + ```bash + git checkout -b feature/your-feature-name + ``` + +4. **Make changes** + - Add code + - Add tests + - Update documentation + +5. **Submit PR** + - Branch must be up-to-date with main + - Include helpful description + - Reference related issues + +--- + +## Resources + +- **Stavanger AI**: See the development.md debate notes +- **GitLab Repository**: https://gitlab.com/trustcafe/trustcafe-api-wrapper +- **WikiTribune Team**: Contact via company channels +- **Packaging**: pyproject.toml for dependencies and metadata + +--- + +**Stay Updated**: Read development.md for ongoing decisions and discussions. This file is intentionally loose to capture evolving thoughts and debates as the project progresses. diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..fdce04c --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,847 @@ +# TrustCafé API Reference + +Complete reference guide for all available jobs in the TrustCafé API wrapper. + +## Table of Contents + +- [Preparation](#preparation) +- [Authentication Jobs](#authentication-jobs) +- [Post Jobs](#post-jobs) +- [Comment Jobs](#comment-jobs) +- [UserProfile Jobs](#userprofile-jobs) +- [Follow Jobs](#follow-jobs) +- [Vote Jobs](#vote-jobs) +- [Reaction Jobs](#reaction-jobs) +- [Notification Jobs](#notification-jobs) +- [Trust Jobs](#trust-jobs) +- [Branch Jobs](#branch-jobs) +- [Feed Jobs](#feed-jobs) +- [Block Jobs](#block-jobs) +- [Mute Jobs](#mute-jobs) +- [Error Handling](#error-handling) + +## Preparation + +Before using jobs, initialize the API client: + +```python +import trustcafeapiwrapper +import os + +API = trustcafeapiwrapper.APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="alpha", # or "production" + debug=False +) + +# Authenticate +API.handle_token() +``` + +## Authentication Jobs + +### sign_in() +Obtains a new access token using OAuth2 client-credentials flow. + +```python +token_data = API.sign_in() +``` + +**Returns:** +```python +{ + "access_token": "string (3-month duration)", + "access_token_timeout": "integer (Unix timestamp)" +} +``` + +**Parameters:** None + +**Error Cases:** +- Invalid client credentials +- Incorrect environment +- Network errors + +--- + +## Post Jobs + +Fetch, create, update, and manage posts. + +### post.get() + +Fetches a specific post by ID or slug. + +```python +post_data = API.run_job('post.get', "post-slug-or-id") +``` + +**Parameters:** +- `post_slug` (str): The post identifier (slug or ID) + +**Returns:** dict - Post data + +**Example:** +```python +post = API.run_job('post.get', "my-first-post") +print(post.get('postText')) +``` + +--- + +### post.create() + +Creates a new post. + +```python +post = API.run_job('post.create', payload) +``` + +**Parameters:** +- `postText` (str): The text content of the post +- `parent` (dict, required): Parent object containing pk and sk + - `pk` (str): Partition key + - `sk` (str): Sort key +- `blurLabel` (str, optional): Label for blurring content +- `cardUrl` (str, optional): URL for link preview card +- `collaborative` (bool, optional): Enable collaborative editing (default: False) + +**Returns:** dict - Created post data + +**Example:** +```python +post = API.run_job('post.create', { + "postText": "This is a new post", + "parent": { + "pk": "maintrunk#maintrunk", + "sk": "maintrunk#maintrunk" + } +}) +``` + +--- + +### post.update() + +Updates an existing post. + +```python +updated_post = API.run_job('post.update', payload) +``` + +**Parameters:** +- `pk` (str): Post partition key +- `sk` (str): Post sort key +- `newPostText` (str, optional): New post content +- Other optional fields to update + +**Returns:** dict - Updated post data + +**Example:** +```python +post = API.run_job('post.update', { + "pk": "post-id-123", + "sk": "post-id-123", + "newPostText": "Updated post content" +}) +``` + +*Note: Full field list requires review of API documentation.* + +--- + +### post.listall() + +Lists all posts. + +```python +posts = API.run_job('post.listall') +``` + +**Parameters:** None + +**Returns:** dict - List of all posts + +**Example:** +```python +all_posts = API.run_job('post.listall') +print(f"Total posts: {len(all_posts.get('Items', []))}") +``` + +--- + +### post.listpublic() + +Lists public posts only. + +```python +public_posts = API.run_job('post.listpublic') +``` + +**Parameters:** None + +**Returns:** dict - List of public posts + +**Example:** +```python +public_posts = API.run_job('post.listpublic') +for post in public_posts.get('Items', []): + print(post.get('pk')) +``` + +--- + +### post.listbybranch() + +Lists posts in a specific branch. + +```python +branch_posts = API.run_job('post.listbybranch', branch_name) +``` + +**Parameters:** +- `branchName` (str): The branch identifier + +**Returns:** dict - List of posts in the branch + +**Example:** +```python +music_posts = API.run_job('post.listbybranch', "music") +``` + +--- + +### post.listbyuserprofile() + +Lists posts by a specific user profile. + +```python +user_posts = API.run_job('post.listbyuserprofile', username) +``` + +**Parameters:** +- `username` (str): The username of the user + +**Returns:** dict - List of posts by user + +**Example:** +```python +journals = API.run_job('post.listbyuserprofile', "philosopher-jon") +``` + +--- + +### post.listremoved() + +Lists removed/deleted posts. + +```python +removed = API.run_job('post.listremoved', post_id) +``` + +**Parameters:** +- `postId` (str): The post identifier + +**Returns:** dict - Removed post data + +**Example:** +```python +removed_post = API.run_job('post.listremoved', "deleted-post-id") +``` + +--- + +## Comment Jobs + +Manage comments on posts. + +### comment.create() + +Creates a new comment. + +```python +comment = API.run_job('comment.create', payload) +``` + +**Parameters:** +- `commentText` (str): Comment content +- `parent` (dict, required): Parent object + - `pk` (str): Partition key + - `sk` (str): Sort key +- Other optional fields as API requires + +**Returns:** dict - Created comment data + +**Example:** +```python +comment = API.run_job('comment.create', { + "commentText": "Great post!", + "parent": { + "pk": "post-id", + "sk": "post-id" + } +}) +``` + +--- + +### comment.listtbypostid() + +Lists comments for a specific post. + +```python +comments = API.run_job('comment.listtbypostid', post_id) +``` + +**Parameters:** +- `postId` (str): The post ID + +**Returns:** dict - List of comments + +**Example:** +```python +comments = API.run_job('comment.listtbypostid', "current-post-id") +for comment in comments.get('Items', []): + print(comment.get('commentText')) +``` + +--- + +## UserProfile Jobs + +Retrieve user profile information. + +### userprofile.get() + +Retrieves a user profile. + +```python +profile = API.run_job('userprofile.get', username) +``` + +**Parameters:** +- `username` (str): The username to retrieve + +**Returns:** dict - User profile data + +**Example:** +```python +profile = API.run_job('userprofile.get', "philosopher-jon") +print(profile.get('username')) +print(profile.get('trustScore')) +``` + +--- + +## Follow Jobs + +Manage follow relationships. + +### follow.follow() + +Follows a user. + +```python +result = API.run_job('follow.follow', username) +``` + +**Parameters:** +- `username` (str): The username to follow + +**Returns:** dict - Follow status + +**Example:** +```python +followed = API.run_job('follow.follow', "another-user") +print(f"Follow status: {followed}") +``` + +--- + +## Vote Jobs + +Cast votes on posts/content. + +### vote.votecast() + +Casts a vote on a post. + +```python +vote = API.run_job('vote.votecast', payload) +``` + +**Parameters:** +- `pk` (str): Object partition key +- `sk` (str): Object sort key +- `voteType` (str): Vote type - "up" or "down" + +**Returns:** dict - Vote status + +**Example:** +```python +voted = API.run_job('vote.votecast', { + "pk": "post-id", + "sk": "post-id", + "voteType": "up" +}) +``` + +--- + +## Reaction Jobs + +Manage reactions to posts, comments, and other content. + +### reaction.reacttosomething() + +React to something (post, comment, etc.). + +```python +reaction = API.run_job('reaction.reacttosomething', payload) +``` + +**Parameters:** +- `objectPK` (str): Object partition key +- `objectSK` (str): Object sort key +- `objectType` (str): Type of object ("post", "comment", etc.) +- `reactionType` (str): Reaction type (depends on API) + +**Returns:** dict - Reaction status + +**Example:** +```python +reacted = API.run_job('reaction.reacttosomething', { + "objectPK": "post-id", + "objectSK": "post-id", + "objectType": "post", + "reactionType": "like" +}) +``` + +--- + +### reaction.getbyparent() + +Get reactions by parent object. + +```python +reactions = API.run_job('reaction.getbyparent', parent_id) +``` + +**Parameters:** +- `objectId` (str): Parent object ID + +**Returns:** dict - List of reactions + +--- + +### reaction.listbyparent() + +List all reactions for a parent object. + +```python +reactions = API.run_job('reaction.listbyparent', parent_id) +``` + +**Parameters:** +- `parentPK` (str): Parent partition key +- `parentSK` (str): Parent sort key + +**Returns:** dict - List of reactions + +--- + +## Notification Jobs + +Manage user notifications. + +### notification.listnotifications() + +Lists all notifications. + +```python +notifications = API.run_job('notification.listnotifications') +``` + +**Parameters:** None + +**Returns:** dict - List of notifications + +**Example:** +```python +notifications = API.run_job('notification.listnotifications') +for notif in notifications.get('Items', []): + print(f"{notif.get('notificationType')}: {notif.get('text')}") +``` + +--- + +### notification.markallasread() + +Marks all notifications as read. + +```python +API.run_job('notification.markallasread') +``` + +**Parameters:** None + +**Returns:** dict - Mark as read status + +**Example:** +```python +API.run_job('notification.markallasread') +``` + +--- + +## Trust Jobs + +Manage trust relationships. + +### trust.createorupdate() + +Create or update a trust relationship. + +```python +trust = API.run_job('trust.createorupdate', payload) +``` + +**Parameters:** +- `pk` (str): Trust identifier (partition key) +- `sk` (str): Trust identifier (sort key) +- `trustType` (str): Trust type ("positive", "negative", etc.) +- Other optional fields + +**Returns:** dict - Trust relationship status + +**Example:** +```python +trust = API.run_job('trust.createorupdate', { + "pk": "trust-id-123", + "sk": "trust-id-123", + "trustType": "positive" +}) +``` + +--- + +### trust.listbyusersinit() + +Lists trust relationships initialized by a user. + +```python +trusts = API.run_job('trust.listbyusersinit', username) +``` + +**Parameters:** +- `username` (str): The user whose trust relationships to list + +**Returns:** dict - List of trust relationships + +**Example:** +```python +my_trusts = API.run_job('trust.listbyusersinit', "your-username") +``` + +--- + +### trust.listbyuserhas() + +Lists trust relationships where a user was the target. + +```python +trusts = API.run_job('trust.listbyuserhas', username) +``` + +**Parameters:** +- `username` (str): The user who was targeted + +**Returns:** dict - List of trust relationships + +--- + +## Branch Jobs + +Manage branches (platform branches or sub-wikis). + +### branch.get() + +Get a specific branch. + +```python +branch = API.run_job('branch.get', branch_name) +``` + +**Parameters:** +- `branchName` (str): The branch identifier + +**Returns:** dict - Branch data + +**Example:** +```python +branch = API.run_job('branch.get', "main") +print(branch.get('branchName')) +``` + +--- + +### branch.listbyname() + +List branches by name prefix. + +```python +branches = API.run_job('branch.listbyname', prefix) +``` + +**Parameters:** +- `prefix` (str): String prefix for branch names + +**Returns:** dict - List of matching branches + +**Example:** +```python +music_branches = API.run_job('branch.listbyname', "music") +``` + +--- + +## Feed Jobs + +Get content feeds from TrustCafé. + +### feed.cafefeed() + +Get the café-wide feed. + +```python +feed = API.run_job('feed.cafefeed') +``` + +**Parameters:** None + +**Returns:** dict - Café feed content + +**Example:** +```python +cafefeed = API.run_job('feed.cafefeed') +print(f"Feed items: {len(cafefeed.get('Items', []))}") +``` + +--- + +### feed.following() + +Get feed for users you follow. + +```python +feed = API.run_job('feed.following') +``` + +**Parameters:** None + +**Returns:** dict - Following feed content + +**Example:** +```python +following_feed = API.run_job('feed.following') +print(f"Following feed items: {len(following_feed.get('Items', []))}") +``` + +--- + +## Block Jobs + +Block users or content. + +### block.[notimplemented] + +Block functionality is defined in the backend but not yet implemented in the wrapper. + +**Status:** TODO + +**Implementation needed:** +- Job function creation +- Payload structure definition +- Error handling + +--- + +## Mute Jobs + +Mute users or content. + +### mute.[notimplemented] + +Mute functionality is pending implementation. + +**Status:** TODO + +**Implementation needed:** +- Job function creation +- Payload structure definition +- Error handling + +--- + +## Error Handling + +### Common Error Cases + +**Authentication Errors** + +```python +try: + profile = API.run_job('userprofile.get', "username") +except Exception as e: + print(f"Authentication required: {e}") + API.handle_token() + profile = API.run_job('userprofile.get', "username") +``` + +**Invalid Parameters** + +```python +try: + post = API.run_job('post.create', { + "postText": "Test", + # Missing required 'parent' field + }) +except Exception as e: + print(f"Validation error: {e}") +``` + +**Network Errors** + +```python +try: + result = API.make_request("GET", "content", "some/path") +except Exception as e: + print(f"Network error: {e}") + # Wait and retry + import time + time.sleep(1) + result = API.make_request("GET", "content", "some/path") +``` + +### Error Response Structure + +API errors typically return: + +```json +{ + "error": "string", + "message": "string", + "details": {} // Optional details +} +``` + +--- + +## Response Structure + +Most API responses follow a similar structure: + +```python +{ + "Items": [ // For list operations + { /* item data */ } + ], + "Count": int, // Number of items + "LastEvaluatedKey": { /* pagination key */ }, // For pagination + "ResponseMetadata": { /* AWS SDK metadata */ } +} +``` + +### Pagination + +For large result sets, results may include pagination: + +```python +def get_all_posts(): + posts = [] + paginator = None + + while True: + response = API.run_job('post.listpublic', params) + posts.extend(response.get('Items', [])) + + has_more = 'LastEvaluatedKey' in response + if not has_more: + break + + # Set params for next page + params = { + 'ExclusiveStartKey': response['LastEvaluatedKey'] + } + + return posts +``` + +--- + +## Best Practices + +### 1. Use Type Hints + +```python +# Good +from typing import Optional, Dict, Any + +def get_post(api: trustcafeapiwrapper.APIClient, post_id: str) -> Dict[str, Any]: + return api.run_job('post.get', post_id) + +# Bad +def get_post(api, post_id): + return api.run_job('post.get', post_id) +``` + +### 2. Validate Responses + +```python +post = API.run_job('post.get', "post-id") +if not post or 'Items' not in post: + raise ValueError("Invalid post data received") + +item = post['Items'][0] +if not item: + raise ValueError("Post item is empty") +``` + +### 3. Error Logging + +```python +import logging + +logger = logging.getLogger(__name__) + +try: + profile = API.run_job('userprofile.get', username) + logger.info(f"Successfully retrieved profile for {username}") +except Exception as e: + logger.error(f"Failed to get profile for {username}: {e}") + raise +``` + +### 4. Timeout Configurations + +The API client has a default timeout of 20 seconds: + +```python +# The make_request method accepts a timeout parameter +# However, current implementation doesn't expose it directly +# Consider updating the APIClient to accept custom timeouts +``` + +### 5. Rate Limiting + +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 diff --git a/docs/CUSTOM_REQUESTS.md b/docs/CUSTOM_REQUESTS.md new file mode 100644 index 0000000..91056b9 --- /dev/null +++ b/docs/CUSTOM_REQUESTS.md @@ -0,0 +1,767 @@ +# Custom Requests Guide + +This guide covers making advanced API calls using the TrustCafé API wrapper's direct request functionality. This is for when jobs or wrappers don't provide the level of control you need. + +## Table of Contents + +- [Overview](#overview) +- [Basic Usage](#basic-usage) +- [Authentication](#authentication) +- [Common Patterns](#common-patterns) +- [Advanced Usage](#advanced-usage) +- [Pagination](#pagination) +- [Error Handling](#error-handling) +- [Examples](#examples) + +## Overview + +### What is make_request? + +`make_request` is the core method of the `APIClient` class that allows you to make raw API calls to TrustCafé endpoints directly. It bypasses jobs and wrappers, giving you complete control over: + +- HTTP methods (GET, POST, PUT, DELETE) +- API endpoints +- Request paths +- Request bodies +- Query parameters +- Authentication + +### When to Use Custom Requests + +**Use `make_request` when:** +- No wrapper or job exists for your operation +- You need a custom HTTP endpoint +- Building complex queries programmatically +- Using specialized API features +- Need maximum flexibility + +**Don't use when:** +- Standard operation is covered by wrappers (too much boilerplate) +- Simpler option exists (jobs/wrappers) +- You're a beginner (wrappers/jobs are easier) + +## Basic Usage + +### Fundamentals + +```python +import trustcafeapiwrapper +import os + +API = trustcafeapiwrapper.APIClient( + client_id=os.getenv("CLIENT_ID"), + client_secret=os.getenv("CLIENT_SECRET"), + env="alpha", + debug=True +) + +API.handle_token() +``` + +### The method signature + +```python +response = API.make_request( + method="GET", + endpoint="content", + path="post/some-id", + data=None, + authenticate=True, + query_params=None +) +``` + +### Parameters Explained + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `method` | str | ✅ Yes | - | HTTP method: "GET", "POST", "PUT", "DELETE" | +| `endpoint` | str | ✅ Yes | - | API service: "content", "auth", "audrey", etc. | +| `path` | str | ✅ Yes | - | API path after endpoint | +| `data` | dict | ❌ No | `None` | Request body for POST/PUT/DELETE | +| `authenticate` | bool | ❌ No | `True` | Include authentication token | +| `query_params` | dict | ❌ No | `None` | Query parameters for GET requests | + +### Simple GET Request + +```python +# Get a post +response = API.make_request( + "GET", + "content", + "post/my-post-id" +) + +print(response) +``` + +### POST Request + +```python +# Create a new resource +response = API.make_request( + "POST", + "content", + "post/new", + data={ + "postText": "My custom post", + "parent": { + "pk": "maintrunk#maintrunk", + "sk": "maintrunk#maintrunk" + } + } +) + +print(response) +``` + +### PUT Request + +```python +# Update an existing resource +response = API.make_request( + "PUT", + "content", + "post/my-post-id", + data={ + "pk": "my-post-id", + "sk": "my-post-id", + "newPostText": "Updated content" + } +) + +print(response) +``` + +### DELETE Request + +```python +# Delete a resource +response = API.make_request( + "DELETE", + "content", + "post/my-post-id" +) + +print(response) +``` + +## Authentication + +### By default, authentication is automatic: + +```python +# This includes the access token in the Authorization header +response = API.make_request( + "GET", + "content", + "post/my-post-id", + authenticate=True # Default +) +``` + +### Making guest requests (no authentication): + +```python +# Useful for public endpoints +response = API.make_request( + "GET", + "content", + "post/public-post", + authenticate=False # No token needed +) +``` + +### Manual token injection (advanced): + +```python +# Sometimes you may need to bypass the token management +response = API.make_request( + "GET", + "content", + "post/some-path", + headers={ + "Authorization": "Bearer your-token-manually-set" + } +) +``` + +## Common Patterns + +### Pagination + +TrustCafé responses may include pagination for large result sets: + +```python +def get_all_posts(limit=100): + """Fetch all posts with pagination""" + + def fetch_page(start_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, + authenticate=True + ) + + return response + + all_posts = [] + start_key = None + + while True: + page = fetch_page(start_key) + + items = page.get('Items', []) + all_posts.extend(items) + + # Check if there are more pages + has_more = 'LastEvaluatedKey' in page + if not has_more or len(items) == 0: + break + + # Set start key for next page + start_key = page['LastEvaluatedKey'] + + return all_posts + + +# Usage +API.handle_token() +all_posts = get_all_posts(limit=50) +print(f"Total posts: {len(all_posts)}") +``` + +### Filtering with Query Parameters + +```python +# Get posts created after a specific date +response = API.make_request( + "GET", + "content", + "post/listpublic", + query_params={ + "creationDateAfter": "2024-01-01T00:00:00Z" + }, + authenticate=True +) + +# Get posts in a specific branch +response = API.make_request( + "GET", + "content", + "post/ref-subwiki/music", + query_params={ + "maxResults": 20, + "showDeleted": False + }, + authenticate=True +) +``` + +### Building Dynamic Paths + +```python +def get_post_by_slug(slug): + """Get a post by slugified ID""" + response = API.make_request( + "GET", + "content", + f"post/id/{slug}", + authenticate=True + ) + return response + +# Usage +post = get_post_by_slug("my-custom-slug-123") +``` + +```python +def get_comments_for_post(post_id): + """Get comments for a specific post""" + response = API.make_request( + "GET", + "content", + f"comment/bypostid/{post_id}", + authenticate=True + ) + return response +``` + +## Advanced Usage + +### Multiple Endpoints + +TrustCafé has multiple API service endpoints: + +```python +# Content API +content_response = API.make_request( + "GET", + "content", + "post/listall", + query_params={"Limit": 10} +) + +# Auth API (for tokens) +auth_response = API.make_request( + "POST", + "auth", + "token", + data={ + "client_id": "your-id", + "client_secret": "your-secret", + "grant_type": "client_credentials" + }, + authenticate=False +) + +# Audrey API (various services) +audrey_response = API.make_request( + "GET", + "audrey", + "some/endpoint", + authenticate=True +) +``` + +### Complex Payloads + +```python +# Building complex payloads for updates +def update_post_completion(post_id, completion_status): + """Update post with new completion status""" + response = API.make_request( + "PUT", + "content", + f"post/{post_id}", + data={ + "pk": post_id, + "sk": post_id, + "postText": "Updated post with completion status", + "completion": completion_status + }, + authenticate=True + ) + return response + + +# Usage +update_post_completion( + "current-post-pk", + { + "isComplete": True, + "completedBy": "username", + "completedAt": "2024-01-01" + } +) +``` + +### Batch Operations + +```python +def batch_create_posts(posts_data): + """Create multiple posts in a single batch""" + + # Build batch request + batch_data = { + "Post": [ + { + "postText": post["text"], + "parent": { + "pk": post["parent_pk"], + "sk": post["parent_sk"] + } + } + for post in posts_data + ] + } + + response = API.make_request( + "POST", + "content", + "post/batch-create", + data=batch_data, + authenticate=True + ) + + return response +``` + +### Error Handling for Custom Requests + +```python +def safe_api_request(method, endpoint, path, data=None): + """ + Safely execute an API request with proper error handling. + """ + try: + response = API.make_request( + method, + endpoint, + path, + data=data, + authenticate=True + ) + + # Check for unexpected errors + if 'error' in response: + raise Exception(f"API Error: {response['error']}") + + return response + + except Exception as e: + print(f"Request failed: {e}") + # Handle error: retry, notify, log, etc. + raise +``` + +## Pagination + +### Pagination Strategy + +When handling paginated results, always check for pagination metadata: + +```python +def fetch_all_content(): + """Fetch all content with pagination handling""" + + all_content = [] + next_token = None + + while True: + params = { + "Limit": 100 + } + + if next_token: + params["ExclusiveStartKey"] = next_token + + try: + response = API.make_request( + "GET", + "content", + "content/list", + query_params=params, + authenticate=True + ) + + except Exception as e: + print(f"Error fetching page: {e}") + break + + # Process current page + items = response.get('Items', []) + all_content.extend(items) + + # Check for next page + has_more = 'LastEvaluatedKey' in response + + if not has_more or len(items) < 100: + print(f"Total items retrieved: {len(all_content)}") + break + + next_token = response['LastEvaluatedKey'] + print(f"Fetching next page (token: {next_token[:10]}...)") + + return all_content +``` + +### Processing Large Datasets + +```python +def process_content_in_batches(batch_size=100): + """Process content in manageable batches""" + + batch_number = 0 + + while True: + batch_number += 1 + print(f"Processing batch {batch_number}...") + + try: + response = API.make_request( + "GET", + "content", + "content/list", + query_params={ + "Limit": batch_size + }, + authenticate=True + ) + + items = response.get('Items', []) + + if not items: + print("No more items to process") + break + + # Process each item in the batch + for item in items: + # Your processing logic here + process_item(item) + + # Check for more items + has_more = 'LastEvaluatedKey' in response + if not has_more: + break + + except Exception as e: + print(f"Error in batch {batch_number}: {e}") + # Retry logic could go here + break + + print(f"Completed processing {batch_number} batches") +``` + +## Error Handling + +### Common Error Scenarios + +```python +def handle_api_request(method, endpoint, path, data=None): + """Robust API request handler""" + + try: + # Make request + response = API.make_request( + method, + endpoint, + path, + data=data, + authenticate=True + ) + + # Check for API-level errors + if 'error' in response: + raise APIError( + endpoint=endpoint, + path=path, + api_error=response['error'], + message=response.get('message', '') + ) + + return response + + except APIError as e: + print(f"API Error: {e}") + # Retry logic or fallback could go here + raise + + except Exception as e: + print(f"Unexpected error: {e}") + # Log the error + log_error(e) + # Could retry or use fallback + raise +``` + +### Retry Logic + +```python +import time +import logging + +logger = logging.getLogger(__name__) + +def retry_on_failure(request_func, max_retries=3, retry_delay=1): + """ + Retry a request function on failure. + + Args: + request_func: Function that makes the API request + max_retries: Maximum number of retry attempts + retry_delay: Seconds to wait between retries + + Returns: + The successful response + """ + for attempt in range(max_retries): + try: + return request_func() + + except Exception as e: + logger.warning( + f"Request failed (attempt {attempt + 1}/{max_retries}): {e}" + ) + + if attempt < max_retries - 1: + logger.info(f"Retrying in {retry_delay} seconds...") + time.sleep(retry_delay) + else: + logger.error("Max retries reached. Giving up.") + raise + + # This line should never be reached due to raise above + raise Exception("Unexpected state in retry function") +``` + +## Examples + +### Example 1: Complex Query + +```python +def search_posts_advanced(search_term, branch="all", limit=50): + """ + Advanced search for posts with filtering. + + Note: This is an EXAMPLE - you'll need to adjust to match + TrustCafé's actual search/query API structure. + """ + + # Build search query + query_data = { + "search": { + "term": search_term, + "filters": { + "branches": [branch] if branch != "all" else None + } + } + } + + response = API.make_request( + "POST", + "content", + "search", + data=query_data, + authenticate=True + ) + + return response +``` + +### Example 2: Custom Analytics Query + +```python +def get_post_engagement_metrics(post_id): + """ + Get engagement metrics for a post. + + Note: This requires mapping to TrustCafé's actual endpoint + and data structure for engagement metrics. + """ + + response = API.make_request( + "GET", + "content", + f"post/{post_id}/analytics", + query_params={ + "metrics": [ + "views", + "likes", + "comments", + "shares" + ], + "timeRange": "7d" + }, + authenticate=True + ) + + return response +``` + +### Example 3: Multi-step Transaction + +```python +def create_post_and_related_content(post_text, related_items): + """ + Create a post and related content in a transaction-like operation. + """ + # Step 1: Create the main post + post_response = API.make_request( + "POST", + "content", + "post", + data={ + "postText": post_text, + "parent": { + "pk": "maintrunk#maintrunk", + "sk": "maintrunk#maintrunk" + } + }, + authenticate=True + ) + + post_id = post_response.get('pk') + + # Step 2: Create related content (if applicable) + if related_items: + for item in related_items: + API.make_request( + "POST", + "content", + "related-item", + data={ + "postId": post_id, + "externalId": item['id'], + "type": item['type'], + "url": item['url'] + }, + authenticate=True + ) + + return post_response +``` + +### Example 4: Conditional Operations + +```python +def conditional_update_post(post_id, updates, condition=None): + """ + Update a post only if a condition is met. + + Note: This maps to CousinDB/GSI-style conditional updates. + Implementation depends on TrustCafé's specific API. + """ + + update_data = { + "pk": post_id, + "sk": post_id, + **updates + } + + if condition: + update_data["condition"] = { + "expression": condition["expression"], + "values": condition["values"] + } + + response = API.make_request( + "PUT", + "content", + f"post/{post_id}", + data=update_data, + authenticate=True + ) + + return response +``` + +--- + +## Summary + +`make_request` provides maximum flexibility for making API calls, but with greater complexity comes the need for careful: + +- **Error handling** - Always wrap in try/except +- **Logging** - Track request/response for debugging +- **Testing** - Verify your custom endpoints +- **Documentation** - Document any custom patterns you discover + +**For most use cases, wrappers and jobs provide the right balance of simplicity and control. Use `make_request` when standard wrappers don't fit your needs.** + +--- + +## Next Steps + +- [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 diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 0000000..080628f --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,469 @@ +# TrustCafé API Wrapper - Getting Started Guide + +This guide will help you quickly set up the TrustCafé API wrapper and start making API calls. + +## Table of Contents + +1. [Installation](#installation) +2. [API Credentials Setup](#api-credentials-setup) +3. [Environment Configuration](#environment-configuration) +4. [First API Call](#first-api-call) +5. [Token Management](#token-management) +6. [Best Practices](#best-practices) +7. [Next Steps](#next-steps) + +## Installation + +### Prerequisites + +Ensure you have: +- Python 3.11 or higher installed +- pip or uv package manager + +### Installing via pip + +```bash +pip install trustcafeapiwrapper +``` + +### Installing via uv (Recommended) + +```bash +uv add trustcafeapiwrapper +``` + +### Installation Verification + +```python +import trustcafeapiwrapper +print(trustcafeapiwrapper.__version__) # Should print the version +``` + +## API Credentials Setup + +### Step 1: Generate Client Credentials + +1. Visit the TrustCafé API Access page: + - **Production**: [https://trustcafe.io/en/myaccount/apiaccess](https://trustcafe.io/en/myaccount/apiaccess) + - **Alpha/Development**: [https://alpha.wts2.net/en/myaccount/apiaccess](https://alpha.wts2.net/en/myaccount/apiaccess) + +2. Click "Create new client credentials key pair" + +3. Select your desired scopes: + - **Full Access**: Select all scopes for maximum flexibility + - **Limited Access**: Choose specific scopes if you only need certain features + +4. Click "Save" + +5. Record your credentials: + - **Client ID**: Your unique application identifier + - **Client Secret**: Your application secret (save this securely!) + +### Step 2: Store Credentials Securely + +**Option A: Environment Variables (Recommended)** + +Use environment variables for production code: + +```bash +# .env file (add to .gitignore) +export TRUSTCAFE_CLIENT_ID="your-client-id-here" +export TRUSTCAFE_CLIENT_SECRET="your-client-secret-here" +export TRUSTCAFE_ENV="production" +``` + +Load the environment variables in your Python code: + +```python +import os +from dotenv import load_dotenv +load_dotenv() + +client_id = os.getenv("TRUSTCAFE_CLIENT_ID") +client_secret = os.getenv("TRUSTCAFE_CLIENT_SECRET") +env = os.getenv("TRUSTCAFE_ENV", "alpha") +``` + +**Option B: Configuration Files** + +For development, you can use configuration files: + +```python +# config.py +CLIENT_ID = "your-client-id" +CLIENT_SECRET = "your-client-secret" +ENVIRONMENT = "alpha" +``` + +**Option C: Secrets Management (Production)** + +For production deployments, use a secrets management system: + +- AWS Secrets Manager +- HashiCorp Vault +- Kubernetes Secrets +- Environment-specific config files in CI/CD + +**Important: Never commit credentials to version control!** + +## Environment Configuration + +The TrustCafé API wrapper supports two environments: + +### Alpha Environment (Development) + +- **Purpose**: Testing, development, staging +- **URL**: `alpha.wts2.net` +- **Best for**: Development and testing before production + +```python +API = APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="alpha" # Alpha environment +) +``` + +### Production Environment + +- **Purpose**: Actual production usage +- **URL**: `trustcafe.io` +- **Best for**: Production deployments + +```python +API = APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="production" # Production environment +) +``` + +### Switching Environments + +```python +# Initialize with alpha +API = APIClient(client_id="id", client_secret="secret", env="alpha") + +# Later, switch to production +API.set_environment("production") +``` + +## First API Call + +### Example: Get Your Profile + +```python +import trustcafeapiwrapper +import os +from dotenv import load_dotenv +load_dotenv() + +# Initialize the API client +API = trustcafeapiwrapper.APIClient( + client_id=os.getenv("TRUSTCAFE_CLIENT_ID"), + client_secret=os.getenv("TRUSTCAFE_CLIENT_SECRET"), + env="alpha", # or "production" + debug=True # Set to True to see request/response details +) + +# Authenticate and get an access token +API.handle_token() + +# Get your profile using a job +profile = API.run_job('userprofile.get', "your-username") + +print("Profile Data:") +print(profile) +``` + +### Example: Create Your First Post + +```python +from trustcafeapiwrapper.wrappers.post.create_post import create_post + +# Create a new post +API.wrapped(create_post( + "Hey there! This is my first post via the API.", + parent_path="/", # Root path + blur_label=None, # Optional: blur content + card_url=None, # Optional: link preview card + collaborative=False # Optional: enable collaboration +)) + +print("Post created successfully!") +``` + +## Token Management + +### Automatic Token Management + +The wrapper automatically manages access tokens: + +```python +# First request - gets and saves token +API.handle_token() + +# Subsequent requests - uses cached token if still valid +API.handle_token() # Returns cached token +``` + +**How it works:** +1. Checks for `token_data_{env}.json` file +2. Verifies token expiration (tokens last ~3 months) +3. Obtains new token if expired +4. Saves token to file for caching + +### Manual Token Control + +For more control: + +```python +# Check if token is valid before making requests +if not API.is_token_valid(): + API.sign_in() # Get new token + +# Or manually set token +API.set_token({ + "access_token": "your_access_token", + "access_token_timeout": 9999999999 # Unix timestamp +}) + +# Verify token validity +if API.is_token_valid(): + print("Token is valid - safe to make requests") +else: + print("Token expired - get a new one") +``` + +### Environment-Specific Tokens + +Different environments use separate token files: + +- `token_data_alpha.json` - Alpha environment tokens +- `token_data_production.json` - Production environment tokens + +This prevents token mixing between environments. + +## Best Practices + +### 1. Environment Variables + +Always use environment variables for credentials: + +```bash +# .env file +TRUSTCAFE_CLIENT_ID= +TRUSTCAFE_CLIENT_SECRET= +TRUSTCAFE_ENV=alpha +``` + +### 2. Error Handling + +Always wrap API calls in try-except blocks: + +```python +try: + API.handle_token() + profile = API.run_job('userprofile.get', "username") +except Exception as e: + print(f"Error: {e}") + # Handle error appropriately +``` + +### 3. Token Refresh Strategy + +For long-running applications: + +```python +# Before making API calls +if not API.is_token_valid(): + API.handle_token() + # Optionally pre-fetch critical data + user_profile = API.run_job('userprofile.get', "username") +``` + +### 4. Logging + +Implement appropriate logging: + +```python +import logging + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +try: + API.handle_token() + profile = API.run_job('userprofile.get', "username") + logger.info(f"Successfully retrieved profile: {profile.get('username')}") +except Exception as e: + logger.error(f"Failed to get profile: {e}") + raise +``` + +### 5. Testing Credentials + +Always test in alpha before production: + +```python +# Test with alpha first +API = APIClient(client_id="test-id", client_secret="test-secret", env="alpha") + +# Verify token works +results = API.handle_token() +print("Alpha environment works!") + +# Then configure production +API = APIClient( + client_id=os.getenv("PROD_CLIENT_ID"), + client_secret=os.getenv("PROD_CLIENT_SECRET"), + env="production" +) +``` + +### 6. Rate Limiting Considerations + +The TrustCafé API may have rate limits. Implement if needed: + +```python +import time +from functools import wraps + +def rate_limit(max_calls=10, period=60): + def decorator(func): + calls = [0] + times = [time.time()] + + @wraps(func) + def wrapper(*args, **kwargs): + now = time.time() + calls[0] += 1 + + # Reset if period has passed + if times[0] + period < now: + times[0] = now + calls[0] = 1 + + # Sleep if necessary + if calls[0] > max_calls: + waited = time.time() - times[0] + 1 + time.sleep(waited) + + times[0] = time.time() + return func(*args, **kwargs) + + return wrapper + return decorator + +# Apply rate limiting +@rate_limit(max_calls=30, period=60) +def api_call(): + return API.make_request(...) +``` + +### 7. Debug Mode in Development + +Use debug mode during development: + +```python +API = APIClient( + client_id="your-id", + client_secret="your-secret", + env="internal", + debug=True # Prints request/response details +) +``` + +## Common Configuration Patterns + +### Production Deployment + +```python +# config.py +import os +from dotenv import load_dotenv + +load_dotenv() + +class Config: + # Production credentials from environment + CLIENT_ID = os.getenv("TRUSTCAFE_PROD_CLIENT_ID") + CLIENT_SECRET = os.getenv("TRUSTCAFE_PROD_CLIENT_SECRET") + ENVIRONMENT = os.getenv("TRUSTCAFE_ENV", "production") + + # Other config + DEBUG = os.getenv("DEBUG", "False") == "True" +``` + +```python +# app.py +from config import Config +import trustcafeapiwrapper + +API = trustcafeapiwrapper.APIClient( + client_id=Config.CLIENT_ID, + client_secret=Config.CLIENT_SECRET, + env=Config.ENVIRONMENT, + debug=Config.DEBUG +) + +API.handle_token() +``` + +### Development/Local + +```python +# config_dev.py +CLASS Config: + CLIENT_ID = "dev-client-id" + CLIENT_SECRET = "dev-client-secret" + ENVIRONMENT = "alpha" + DEBUG = True +``` + +``` +# .env file +DEBUG=True +LOG_LEVEL=INFO +``` + +## Troubleshooting Setup Issues + +### Issue: "Client ID not found" + +**Solution**: Ensure you've generated client credentials from the TrustCafé admin page. + +### Issue: "Client secret cannot be retrieved" + +**Solution**: Client secrets can only be retrieved when first created. Create a new pair if needed. + +### Issue: "Invalid client credentials" + +**Solution**: Double-check client_id and client_secret. Verify you're using the correct environment. + +### Issue: "Token expired" + +**Solution**: Call `API.handle_token()` to get a new token. Check your token file is writable. + +### Issue: "Permission denied" when writing token file + +**Solution**: Ensure the working directory is writable. Use absolute paths or configure a writable location. + +## Next Steps + +Now that you're set up: + +1. **Read the API Reference**: Learn about all available jobs and wrappers + - [API Reference](api_reference.md) + +2. **Explore Wrappers**: Try the high-level wrappers for common tasks + - [Wrappers Guide](wrappers.md) + +3. **Make Custom Requests**: Learn to make advanced API calls manually + - [Custom Requests Guide](custom_requests.md) + +4. **Handle Errors**: Learn about common errors and how to handle them + - [Troubleshooting Guide](troubleshooting.md) + +5. **Contribute**: Help complete the wrapper with additional jobs + - [Development Guide](development.md) diff --git a/docs/INDEX.md b/docs/INDEX.md new file mode 100644 index 0000000..2bc5f3e --- /dev/null +++ b/docs/INDEX.md @@ -0,0 +1,162 @@ +# TrustCafé API Wrapper - Documentation Index + +Complete documentation for the TrustCafé API wrapper. + +## Getting Started + +**New to the wrapper?** Start here. + +- [Getting Started Guide](GETTING_STARTED.md) - Complete setup and configuration guide +- [README.md](../README.md) - Overview package information and installation + +## Core Usage + +**Learn how to use the API wrapper effectively.** + +- [Wrappers Guide](WRAPPERS.md) - High-level wrappers for common operations +- [API Reference](API_REFERENCE.md) - Complete API job reference +- [Custom Requests Guide](CUSTOM_REQUESTS.md) - Making advanced API calls manually + +## Development + +**Contributing or extending the wrapper.** + +- [Development Guide](../development.md) - Development notes, design decisions, and known limitations +- [Examples](../README.md#examples) - Real-world usage examples + +## Support and Troubleshooting + +**Having issues?** Find solutions here. + +- [Troubleshooting Guide](TROUBLESHOOTING.md) - Common issues and solutions + +--- + +## Quick Links + +### For New Users +1. [Installation](../README.md#installation) +2. [Authentication Setup](GETTING_STARTED.md#api-credentials-setup) +3. [First API Call](GETTING_STARTED.md#first-api-call) +4. [Basic Usage](../README.md#quick-start) + +### For In-Depth Learning +1. [Wrappers](WRAPPERS.md) - Start here for easiest usage +2. [API Reference](API_REFERENCE.md) - Deep dive into all jobs +3. [Creating Custom Wrappers](WRAPPERS.md#creating-custom-wrappers) + +### For Developers +1. [Extending the Wrapper](../development.md#extending-the-wrapper) +2. [Testing Guide](../development.md#testing-guide) +3. [Contributing](../development.md#contributing) +4. [Known Limitations](../development.md#known-limitations) + +--- + +## Documentation Contents + +### Getting Started (GETTING_STARTED.md) +- Installation steps +- API credentials setup +- Environment configuration +- First API call example +- Token management best practices +- Setup patterns for different environments + +### Wrappers Guide (WRAPPERS.md) +- Overview of wrapper system +- Post wrapper documentation (create_post, update_post) +- Comment wrapper documentation (create_comment) +- Wrapper best practices +- Wrapper vs Job comparison +- Creating custom wrappers + +### API Reference (API_REFERENCE.md) +- All available Jobs +- Complete job parameters and return values +- Authentication job documentation +- Post, Comment, UserProfile, Follow, Vote jobs +- Reaction, Notification, Trust, Branch jobs +- Feed operations +- Error handling patterns +- Pagination guide + +### Custom Requests Guide (CUSTOM_REQUESTS.md) +- Using the `make_request` method +- Advanced usage patterns +- Pagination strategies +- Batch operations +- Error handling for custom calls +- Complex query examples + +### Troubleshooting Guide (TROUBLESHOOTING.md) +- Authentication issues +- Connection problems +- Token management errors +- API call issues +- Environment issues +- Data handling problems +- Performance issues +- Debugging strategies + +### Development Guide (development.md) +- Project structure +- Design decisions and debates +- Known limitations +- Missing features +- Future enhancements +- Contributing guidelines +- Testing guide +- Extension instructions + +--- + +## Support + +If you can't find the answer you're looking for: + +1. **Check the FAQs section** - Common questions and answers +2. **Review the Troubleshooting guide** - For specific error handling +3. **Contact WikiTribune team** - Via company channels +4. **File an issue on GitLab** - [Repository Issues](https://gitlab.com/trustcafe/trustcafe-api-wrapper/-/issues) + +--- + +## Changelog + +Document version updates should be listed here. + +### v0.2.0 (Current) +- Complete rewrite and enhancement of all documentation +- Added comprehensive API reference +- Added wrappers guide +- Added custom requests guide +- Added detailed troubleshooting section +- Enhanced development documentation +- Improved README.md structure and examples + +### v0.1.0.13 +- Initial release with basic documentation +- Minimum set of jobs and wrappers + +--- + +## Contributing to Documentation + +If you find any documentation issues: + +1. **Typos or errors** - Correct in the markdown file +2. **Outdated examples** - Update with correct code +3. **Missing information** - Add to the appropriate section +4. **Unclear explanations** - Improve wording for clarity + +When contributing documentation remember: +- Keep examples working +- Maintain consistent formatting +- Use proper markdown syntax +- Link to related documentation +- Update this INDEX.md if structure changes + +--- + +**Last Updated:** April 17, 2026 diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..523127f --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,763 @@ +# Troubleshooting Guide + +Common issues and solutions when using the TrustCafé API wrapper. + +## Table of Contents + +- [Authentication Issues](#authentication-issues) +- [Connection Issues](#connection-issues) +- [Token Management Issues](#token-management-issues) +- [API Call Issues](#api-call-issues) +- [Environment Issues](#environment-issues) +- [Data Handling Issues](#data-handling-issues) +- [Performance Issues](#performance-issues) +- [Debugging Strategies](#debugging-strategies) + +## Authentication Issues + +### Issue: "Invalid client credentials" + +**Symptoms:** +``` +ValueError: Client credentials invalid +``` + +**Causes:** +- Incorrect client_id or client_secret +- Using alpha credentials on production environment (or vice versa) +- Credentials haven't been saved yet from the TrustCafé admin panel + +**Solutions:** + +1. Verify credentials from the admin panel +2. Ensure environment matches credentials: + ```python + # Check your credentials are for the right environment + print(f"Environment: {env}") # Should match where credentials were created + ``` + +3. Generate new credentials if needed: + - Visit: https://www.trustcafe.io/en/myaccount/apiaccess + - Click "Create new client credentials key pair" + - Save the new credentials + +4. Verify credentials aren't expired (trustcafe-side expiration) + +--- + +### Issue: "Token expired" + +**Symptoms:** +``` +Exception: Token has expired +``` + +**Causes:** +- Access token duration has expired (~3 months from creation) +- Time mismatch between systems + +**Solutions:** + +1. Automatically refresh token: + ```python + # Check token validity and refresh if needed + if not API.is_token_valid(): + API.handle_token() + ``` + +2. Pre-load tokens before long operations: + ```python + # Ensure token is fresh before making many requests + API.handle_token() + + # Now all API calls will succeed + ``` + +3. Provide longer expiration time for testing: + ```python + # Manual token with long expiration + API.set_token({ + "access_token": "your-token", + "access_token_timeout": 9999999999 # Far future date + }) + ``` + +--- + +### Issue: "Permission denied" when making requests + +**Symptoms:** +``` +ForbiddenError: Insufficient permissions for this API endpoint +``` + +**Causes:** +- Client credentials lack required scopes +- Not authenticated (no access token) + +**Solutions:** + +1. Verify scopes on the admin panel +2. Ensure authentication is enabled in all requests: + ```python + # Make sure to authenticate + response = API.make_request( + "GET", + "content", + "some/path", + authenticate=True # Ensure this is True + ) + + # For guest requests (if allowed) + response = API.make_request( + "GET", + "content", + "some/path", + authenticate=False + ) + ``` + +--- + +## Connection Issues + +### Issue: "No internet connection" or "Connection refused" + +**Symptoms:** +``` +ConnectionError: An error occurred while making the request +``` + +**Causes:** +- Network connectivity issues +- Firewall blocking requests +- API service unavailable + +**Solutions:** + +1. Check network connectivity: + ```bash + # Test connectivity to alpha/production endpoints + ping alpha.wts2.net + ping trustcafe.io + ``` + +2. Verify firewall rules allow HTTPS requests + +3. Try alternative network (mobile hotspot vs. Wi-Fi) + +4. Check if TrustCafé services are up: + - Production: https://trustcafe.io + - Alpha: https://alpha.wts2.net + +5. Add retry logic: + ```python + import time + + def retry_request(request_func, max_retries=3): + for attempt in range(max_retries): + try: + return request_func() + except ConnectionError as e: + if attempt < max_retries - 1: + time.sleep(1) + else: + raise + ``` + +--- + +### Issue: "DNS resolution failed" + +**Symptoms:** +``` +requests.exceptions.ConnectionError: DNS resolution failed +``` + +**Causes:** +- DNS server issues +- VPN interfering with DNS +- Incorrect hostname + +**Solutions:** + +1. Check DNS: + ```bash + nslookup alpha.wts2.net + nslookup trustcafe.io + ``` + +2. Try changing DNS server: + ```bash + # Use Google DNS (8.8.8.8, 8.8.4.4) + # Or Cloudflare (1.1.1.1, 1.0.0.1) + ``` + +3. Disable VPN temporarily +4. Check configuration for correct hostnames (alpha.wts2.net vs trustcafe.io) + +--- + +## Token Management Issues + +### Issue: "Token file not found" + +**Symptoms:** +``` +FileNotFoundError: Token data file 'token_data_alpha.json' not found +``` + +**Causes:** +- First-time run +- Token directory not created +- Wrong path specified + +**Solutions:** + +1. API `_handle_token` automatically creates file on first use + +2. Ensure working directory is writable + +3. Specify custom token file path: + ```python + API.handle_token(token_data_path="my/custom/path/token_data.json") + ``` + +4. Manually create token file: + ```python + token_data = API.sign_in() + with open("token_data_alpha.json", "w") as f: + json.dump(token_data, f) + ``` + +--- + +### Issue: "Permission denied: cannot write token file" + +**Symptoms:** +``` +PermissionError: [Errno 13] Permission denied: 'token_data_alpha.json' +``` + +**Causes:** +- Working directory not writable +- File locked by another process +- Insufficient permissions + +**Solutions:** + +1. Ensure writable directory: + ```python + import os + + # Use a writable directory + writable_dir = os.path.expanduser("~") # Home directory + API.handle_token(token_data_path=f"{writable_dir}/tokens/token_data.json") + ``` + +2. Check file permissions: + ```bash + ls -la token_data_alpha.json + chmod 644 token_data_alpha.json + ``` + +3. Try with a custom directory: + ```python + import tempfile + + # Use temp directory + with tempfile.TemporaryDirectory() as tmpdir: + API.handle_token(token_data_path=f"{tmpdir}/token_data.json") + ``` + +--- + +### Issue: "Token file corrupted" + +**Symptoms:** +``` +json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) +``` + +**Causes:** +- File corruption during write +- File truncated by other process + +**Solutions:** + +1. Delete corrupted token file: + ```bash + rm token_data_alpha.json + ``` + +2. Let wrapper recreate it + +3. Check for concurrent token writes: + ```python + from threading import Lock + + token_lock = Lock() + + def safe_handle_token(): + with token_lock: + API.handle_token() + ``` + +--- + +## API Call Issues + +### Issue: "400 Bad Request" or "Invalid parameter" + +**Symptoms:** +``` +ValueError: Invalid parameter or data format +400 Bad Request +``` + +**Causes:** +- Missing required parameters +- Incorrect parameter format +- Parameter validation failure + +**Solutions:** + +1. Check parameter requirements in API Reference: + - See [API Reference](api_reference.md) for each job + +2. Verify parameter types: + ```python + # Use correct types + API.run_job('post.create', { + "postText": "text string", # Should be string + "blurLabel": None, # None is okay + # Wrong: "blurLabel": "" (empty string) + }) + ``` + +3. Verify required parameters: + ```python + # Example for post.create + required_fields = ['postText', 'parent'] + + if 'parent' not in data: + raise ValueError("Required field 'parent' is missing") + ``` + +4. Use debug mode to see request: + ```python + API = APIClient(debug=True) + + # Now you'll see the exact data being sent + API.run_job('post.create', data) + ``` + +--- + +### Issue: "404 Not Found" + +**Symptoms:** +``` +FileNotFoundError: Endpoint not found +404 Not Found +``` + +**Causes:** +- Wrong endpoint or path +- Resource doesn't exist +- Branch/post not found + +**Solutions:** + +1. Verify the path is correct +2. Check if resource exists: + ```python + # Verify branch exists + branch = API.run_job('branch.get', "branch-name") + + if not branch: + print("Branch does not exist") + + # Verify post exists + post = API.run_job('post.get', "post-id") + + if not post: + print("Post does not exist") + ``` + +3. Check for spelling mistakes in paths + +4. Use debug mode to see exact path being sent: + ```python + API = APIClient(debug=True) + API.run_job('post.create', data) # See exact request path + ``` + +--- + +### Issue: "500 Internal Server Error" + +**Symptoms:** +``` +ConnectionError: An error occurred +500 Internal Server Error +``` + +**Causes:** +- TrustCafé server error +- Database issues on server side +- Unhandled edge case + +**Solutions:** + +1. Wait a moment and retry +2. Check TrustCafé status +3. Try at a different time +4. Consider this transient error: + ```python + import time + + def retry_server_error(func, max_retries=3): + for attempt in range(max_retries): + try: + return func() + except ConnectionError as e: + if str(e).find("500") != -1: + if attempt < max_retries - 1: + time.sleep(2 ** attempt) # Exponential backoff + else: + raise + ``` + +--- + +### Issue: "Rate limit exceeded" + +**Symptoms:** +``` +429 Too Many Requests +``` + +**Causes:** +- Too many API calls too quickly +- Gratuitous API usage + +**Solutions:** + +1. Implement rate limiting (see main README for example) + +2. Space out your API calls: + ```python + import time + + def spaced_request(request_func, delay=1): + result = request_func() + if hasattr(request_func, 'call_count'): + request_func.call_count += 1 + if request_func.call_count > 30: + time.sleep(2) + return result + ``` + +3. Use pagination for large result sets + +4. Consider caching results when possible + +--- + +## Environment Issues + +### Issue: "Environment 'invalid' is not valid" + +**Symptoms:** +``` +ValueError: Environment 'staging' is not valid. Must be one of: ['alpha', 'production'] +``` + +**Causes:** +- Typo in environment name +- Passing only environment once +- Environment changed incorrectly + +**Solutions:** + +1. Use correct environment names: + - "alpha" (development/staging) + - "production" (live) + +2. Avoid modifying environment mid-session after API init + +3. Reinitialize if you need to switch: + ```python + # Create new API client for new environment + API_prod = APIClient( + client_id="prod-id", + client_secret="prod-secret", + env="production" + ) + + # Old API still has old environment + print(API.environment) # "alpha" + ``` + +--- + +### Issue: "Endpoint 'invalid' is not defined" + +**Symptoms:** +``` +ValueError: Endpoint 'noservices' is not defined in the API client. +``` + +**Causes:** +- Typo in endpoint name +- Custom endpoint not configured +- Server-side endpoint changed + +**Solutions:** + +1. Check available endpoints in APIClient code +2. Use correct endpoints: "content", "auth", "audrey", etc. +3. Review documentation for correct API structure + +--- + +## Data Handling Issues + +### Issue: "JSON decode error" or "Response is not valid JSON" + +**Symptoms:** +``` +ValueError: Response is not valid JSON: Expecting value: line 1 column 1 (char 0) +``` + +**Causes:** +- Server returning non-JSON response +- Network error causing incomplete response +- Empty response + +**Solutions:** + +1. Add error handling: + ```python + try: + response = API.make_request("GET", "content", "path") + response_json = response.json() + except json.JSONDecodeError as e: + print(f"Failed to parse JSON: {e}") + print(f"Response was: {response}") + ``` + +2. Check if server is responding correctly + +3. Verify network connectivity + +4. Check for errors in response: + ```python + if 'error' in response: + raise Exception(f"API Error: {response['error']}") + ``` + +--- + +### Issue: Empty response or missing data + +**Symptoms:** +``` +expected to find Items in response, but didn't +``` + +**Causes:** +- Query returns no results +- Pagination not handled +- Response structure misunderstanding + +**Solutions:** + +1. Check for empty results: + ```python + response = API.make_request("GET", "content", "some/path") + + if 'Items' not in response or not response.get('Items'): + print("No results found") + ``` + +2. Check pagination status: + ```python + if 'LastEvaluatedKey' in response: + print("More results available, need pagination") + ``` + +3. Verify response structure understanding + +--- + +## Performance Issues + +### Issue: "Timeout" errors + +**Symptoms:** +``` +requests.exceptions.Timeout: Request timed out +``` + +**Causes:** +- Network slowness +- Large result sets +- Slow server response + +**Solutions:** + +1. Increase timeout: + ```python + # Unfortunately, current implementation doesn't expose timeout + # This would need updating in apiclient.py + + # If you control code, you can modify the make_request method + # to accept a timeout parameter + ``` + +2. Reduce request complexity + +3. Implement pagination for large datasets + +4. Use async if available + +--- + +### Issue: Slow token refresh + +**Symptoms:** +- Initial request slow +- Token refresh takes too long + +**Causes:** +- Network latency to auth endpoint +- Server-side delays + +**Solutions:** + +1. Pre-load token: + ```python + # Get token before doing anything time-sensitive + API.handle_token() + + # All subsequent calls will use cached token + ``` + +2. Use manual token for frequent operations: + ```python + API.set_token({ + "access_token": token, + "access_token_timeout": 9999999999 + }) + ``` + +--- + +## Debugging Strategies + +### Enable Debug Mode + +```python +API = APIClient( + client_id="your-id", + client_secret="your-secret", + env="alpha", + debug=True # Enables verbose logging +) +``` + +This will print: +- Request method +- Full URL +- Headers +- Request payload + +### Add Logging + +```python +import logging + +logging.basicConfig(level=logging.DEBUG) + +logger = logging.getLogger(__name__) + +try: + result = API.run_job('post.create', data) + logger.debug(f"Success: {result}") +except Exception as e: + logger.error(f"Failed: {e}", exc_info=True) +``` + +### Verify Request Flow + +```python +def trace_request(func, *args, **kwargs): + """Trace each request""" + import inspect + + # Get function name + func_name = inspect.getmember(BuiltinFunctionType, func).__name__ + + print(f"Calling: {func_name}") + print(f"Args: {args}") + print(f"Kwargs: {kwargs}") + + result = func(*args, **kwargs) + + print(f"Result: {result}") + return result +``` + +### Network Inspection + +```python +import requests + +# Direct inspection of requests +import logging +requests_log = logging.getLogger("urllib3") +requests_log.setLevel(logging.DEBUG) + +requests_log.propagate = True +``` + +--- + +## Getting Help + +If you've tried all of the above and still have issues: + +1. **Check the Code**: Review the latest version on GitLab + - [GitLab Repository](https://gitlab.com/trustcafe/trustcafe-api-wrapper) + +2. **Check TrustCafé Status**: Verify services are up + - [Production](https://trustcafe.io) + - [Alpha](https://alpha.wts2.net) + +3. **Review Contributions**: Check for known issues + - Open an issue on GitLab + - Check if a fix is already in progress + +4. **Contact Support**: For resolved but unreleased issues + - WikiTribune team + - Build Product Slack/Discord + +--- + +## Quick Reference + +Common error codes: + +- **400 Bad Request**: Invalid parameters (check API Reference) +- **401 Unauthorized**: Invalid/missing token (call `handle_token()`) +- **403 Forbidden**: Insufficient permissions (check scopes) +- **404 Not Found**: Wrong path or resource doesn't exist +- **429 Too Many Requests**: Rate limit exceeded (apply rate limiting) +- **500 Internal Server Error**: Service error (wait and retry) +- **Timeout**: Network issues (check connectivity) + +--- + +**Remember**: Most issues are easily solved with proper error handling, verification of credentials, and rate limiting. diff --git a/docs/WRAPPERS.md b/docs/WRAPPERS.md new file mode 100644 index 0000000..04c2a5c --- /dev/null +++ b/docs/WRAPPERS.md @@ -0,0 +1,509 @@ +# TrustCafé API Wrapped Functions Guide + +This guide covers the high-level wrapper functions provided by the TrustCafé API wrapper. Wrappers simplify common operations by handling payload preparation and job execution automatically. + +## Table of Contents + +- [Overview](#overview) +- [Post Wrappers](#post-wrappers) +- [Comment Wrappers](#comment-wrappers) +- [Wrappers Best Practices](#wrappers-best-practices) +- [Wrapper vs Job](#wrapper-vs-job) + +## Overview + +### What are Wrappers? + +Wrappers are pre-built functions that encapsulate common TrustCafé API operations. They: + +- **Simplify usage**: Less boilerplate code +- **Handle payload preparation**: Automatically structure requests +- **Prevent errors**: Built-in validation and defaults +- **Self-documenting**: Clear parameter names and descriptions + +### When to Use Wrappers + +**Use Wrappers for:** +- Creating and managing content (posts, comments) +- Standard operations with predictable inputs +- Reducing code complexity +- Frequently used patterns + +**Use Jobs or Custom Requests when:** +- More control is needed +- Custom parameter combinations +- Operations not covered by wrappers +- Uncommon workflows + +### Quick Example + +```python +# Using a wrapper (RECOMMENDED) +from trustcafeapiwrapper.wrappers.post.create_post import create_post + +API.wrapped(create_post( + "This is my post content", + parent_path="/music" +)) + +# Using a job (for custom control) +API.run_job('post.create', { + "postText": "Different content", + "parent": { + "pk": "parents-key", + "sk": "parents-key" + } +}) +``` + +## Post Wrappers + +### create_post() + +Creates a new post with simplified parameters. + +```python +from trustcafeapiwrapper.wrappers.post.create_post import create_post + +API.wrapped(create_post( + post_text="Post content here", + parent_path="/branch-name", + blur_label=None, + card_url=None, + collaborative=False +)) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `post_text` | str | **Required** | The text content of the post | +| `parent_path` | str | `"/"` | Branch path where the post will be created | +| `blur_label` | str | `None` | Optional label for blurring content | +| `card_url` | str | `None` | Optional URL for card preview | +| `collaborative` | bool | `False` | Enable collaborative editing | + +**Returns:** dict - Job execution result, contains success information + +**Example - Basic Post:** + +```python +from trustcafeapiwrapper.wrappers.post.create_post import create_post + +API.handle_token() + +API.wrapped(create_post( + "Welcome to TrustCafé!", + parent_path="/" +)) +``` + +**Example - Post in a Specific Branch:** + +```python +API.wrapped(create_post( + "Music discussion thread", + parent_path="/music", + blur_label=None, + card_url=None, + collaborative=False +)) +``` + +**Example - Blurred Post:** + +```python +API.wrapped(create_post( + "Sensitive information", + parent_path="/", + blur_label="secret", # Will blur until labeled + card_url=None, + collaborative=False +)) +``` + +**Example - Collaborative Post:** + +```python +API.wrapped(create_post( + "Research paper draft - collaborative editing", + parent_path="/research", + blur_label=None, + card_url=None, + collaborative=True # Enables real-time collaboration +)) +``` + +Example of performing multi-step integration with create_post and a linked resource: +```python +# Create post +created = API.wrapped(create_post("Paper title / author", parent_path="/research")) + +# If response includes pk/sk, attach a linked item (structure depends on TrustCafé's link feature) +API.run_job('some.link.create', { + "fromPK": "paper", + "fromSK": created.get('pk'), + "toPK": "external-source", + "toSK": created.get('sk') +}) +``` + +### update_post() + +Updates an existing post. + +```python +from trustcafeapiwrapper.wrappers.post.update_post import update_post + +API.wrapped(update_post( + post_id="post-uuid", + new_post_text="New post content" +)) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `post_id` | str | **Required** | The post to update (pk/sk) | +| `new_post_text` | str | **Required** | The new post content | +| Optional fields per the API (e.g., blurLabel, cardUrl, etc.) + +**Returns:** dict - Updated post data + +**Important:** The post_id parameter should be set to the post's pk/sk. The wrapper internally maps the update parameters to the API's update payload structure. + +**Example - Update Text:** + +```python +from trustcafeapiwrapper.wrappers.post.update_post import update_post + +API.handle_token() + +API.wrapped(update_post( + post_id="current-pk-same-as-sk", + new_post_text="Updated content here" +)) +``` + +**Example - Update with Optional Fields:** + +```python +API.wrapped(update_post( + post_id="current-pk-same-as-sk", + new_post_text="Updated with new card", + card_url="https://example.com/resource" +)) +``` + +**Example - Update Blur Label:** + +```python +API.wrapped(update_post( + post_id="current-pk-same-as-sk", + blur_label="sensitive" +)) +``` + +See update_post.py for mapping of updated fields to the server payload. + +## Comment Wrappers + +### create_comment() + +Creates a new comment on a post. + +```python +from trustcafeapiwrapper.wrappers.comment.create_comment import create_comment + +API.wrapped(create_comment( + comment_text="This is a comment", + parent_path="/" +)) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `comment_text` | str | **Required** | The comment content | +| `parent_path` | str | **Required** | Path to the parent post where comment will be created | + +**Returns:** dict - Job execution result, contains success information + +**Example - Basic Comment:** + +```python +from trustcafeapiwrapper.wrappers.comment.create_comment import create_comment + +API.handle_token() + +API.wrapped(create_comment( + "Great post! I really enjoyed reading it.", + parent_path="/" +)) +``` + +**Example - Comment on a Post:** + +```python +# First get the post to find its path +post = API.run_job('post.get', "target-post-id") + +# Then create a comment +API.wrapped(create_comment( + "I agree with your point about this.", + parent_path="/music" # Path to the post +)) +``` + +**Example - Collaborative Comment:** + +```python +API.wrapped(create_comment( + "Let's work on this together!", + parent_path="/current-post" +)) +``` + +## Wrappers Best Practices + +### 1. Provide Context in Parent Paths + +**Good - Explicit paths:** + +```python +API.wrapped(create_post( + "Content", + parent_path="/music/discussion" # Clear and specific +)) +``` + +**Bad - Empty paths:** + +```python +API.wrapped(create_post( + "Content", + parent_path="/" # Default root - less clear intention +)) +``` + +### 2. Handle Phone Numbers in Content + +When phone numbers appear in text, leave them as-is. The API wraps the input string, so `555-1234` stays `555-1234`. No HTML is added. + +### 3. Path Normalization + +Paths are used directly without extra normalization: + +```python +# Use with leading slash +API.wrapped(create_post( + "Content", + parent_path="/music" +)) + +# Or without (both should work) +API.wrapped(create_post( + "Content", + parent_path="music" +)) +``` + +### 4. Validate Parent Exists + +It's good practice to verify the parent path exists: + +```python +# Check if branch exists +branches = API.run_job('branch.listbyname', "music") + +if branches.get('Items'): + # Branch exists, proceed with post + API.wrapped(create_post( + "Content", + parent_path="/music" + )) +else: + print("Branch 'music' does not exist") +``` + +### 5. Handle Responses + +```python +from trustcafeapiwrapper.wrappers.post.create_post import create_post + +response = API.wrapped(create_post( + "My post", + parent_path="/music" +)) + +# Check for success +if response and 'success' in response: + print("Post created successfully!") + print(f"Post ID: {response.get('pk')}") +else: + print("Failed to create post") + print(f"Error: {response.get('error')}") +``` + +### 6. Error Handling + +```python +from trustcafeapiwrapper.wrappers.post.create_post import create_post +import logging + +logger = logging.getLogger(__name__) + +try: + API.handle_token() # Ensure authenticated + API.wrapped(create_post( + "Post content", + parent_path="/music" + )) + logger.info("Post created successfully") +except Exception as e: + logger.error(f"Failed to create post: {e}") + # Handle error - maybe retry, notify admin, etc. +``` + +## Wrapper vs Job + +| Feature | Wrappers | Jobs | +|---------|----------|------| +| **Simplicity** | ⭐⭐⭐⭐⭐ | ⭐⭐ | +| **Flexibility** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| **Default Values** | ✅ Yes | ❌ No | +| **Parameter Validation** | ✅ Yes | ❌ No | +| **Common Operations** | ✅ Many | ❌ None | +| **Custom Operations** | ❌ No | ✅ Yes | +| **Learning Curve** | Low | High | + +### Replacement Examples + +**Example 1: Create Post** + +```python +# Wrapper - Simple +API.wrapped(create_post( + "Content", + parent_path="/music" +)) + +# Job - More complex +API.run_job('post.create', { + "postText": "Content", + "parent": { + "pk": "encode(parent_path)", + "sk": "encode(parent_path)" + } +}) +``` + +**Example 2: Update Post** + +```python +# Wrapper +API.wrapped(update_post( + post_id="current-pk-same-as-sk", + new_post_text="New content" +)) + +# Job +API.run_job('post.update', { + "pk": "current-pk-same-as-sk", + "sk": "current-pk-same-as-sk", + "newPostText": "New content" +}) +``` + +**Example 3: Create Comment** + +```python +# Wrapper +API.wrapped(create_comment( + "My comment", + parent_path="/post-id" +)) + +# Job +API.run_job('comment.create', { + "commentText": "My comment", + "parent": { + "pk": "post-id", + "sk": "post-id" + } +}) +``` + +### When to Use Each + +**Use Wrappers When:** +- Common operation (create/update post/comment) +- Multiple optional parameters +- Want concise, readable code +- Acceptable default behavior + +**Use Jobs When:** +- Rarely used operation +- Very specific parameter combinations +- Custom job names unsupported by wrappers +- Need maximum flexibility + +## Creating Custom Wrappers + +You can create your own wrappers for repeated operations: + +```python +# Custom post wrapper +def create_in_branch(API, branch: str, content: str, create_card: bool = False): + """ + Helper wrapper for creating posts in specific branches. + + Args: + API: The APIClient instance + branch: Branch name (e.g., "music", "science") + content: Post content + create_card: Whether to create a card URL + + Returns: + dict: API response + """ + card_url = "https://example.com/card" if create_card else None + + from trustcafeapiwrapper.wrappers.post.create_post import create_post + + return API.wrapped(create_post( + content, + parent_path=f"/{branch}", + card_url=card_url + )) + + +# Usage +API.handle_token() +result = create_in_branch(API, "music", "New music discussion") + +# Or with card +result = create_in_branch(API, "science", "Research paper abstract", create_card=True) +``` + +--- + +## Summary + +Wrappers provide a convenient, safe way to perform common operations with the TrustCafé API. For most use cases, wrappers reduce boilerplate and prevent configuration errors. However, jobs offer greater flexibility when standard wrappers don't meet your needs. + +**Choose wisely:** +- Wrappers for standard operations and simplicity +- Jobs for custom operations and maximum control +- Custom wrappers for repeated patterns + +--- + +## Next Steps + +- [API Reference](api_reference.md) - Complete list of all API jobs +- [Custom Requests Guide](custom_requests.md) - Making advanced API calls manually +- [Examples](../README.md#examples) - Real-world usage examples