last-updates
This commit is contained in:
@@ -84,6 +84,7 @@ new_page = client.pages.create(PageCreate(
|
||||
### **For Maintainers**
|
||||
- **[Architecture](docs/wikijs_sdk_architecture.md)**: Technical design and patterns
|
||||
- **[Development Plan](docs/wikijs_sdk_release_plan.md)**: Complete roadmap and milestones
|
||||
- **[PyPI Publishing](docs/PIP_INSTRUCTIONS.md)**: Complete guide to publishing on PyPI
|
||||
- **[AI Coordination](CLAUDE.md)**: AI-assisted development workflow
|
||||
|
||||
---
|
||||
|
||||
232
docs/PIP_INSTRUCTIONS.md
Normal file
232
docs/PIP_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# PyPI Publishing Instructions
|
||||
|
||||
This document provides step-by-step instructions for publishing the Wiki.js Python SDK to PyPI.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Package has been built and tested locally
|
||||
- All tests pass with >85% coverage
|
||||
- Documentation is complete and up-to-date
|
||||
- GitHub repository is properly configured
|
||||
|
||||
## Pre-Publishing Checklist
|
||||
|
||||
### 1. Update Repository URLs
|
||||
Replace `yourusername` with your actual GitHub username in the following files:
|
||||
|
||||
**setup.py** (lines 44, 46-48):
|
||||
```python
|
||||
url="https://github.com/YOUR_USERNAME/wikijs-python-sdk",
|
||||
project_urls={
|
||||
"Bug Reports": "https://github.com/YOUR_USERNAME/wikijs-python-sdk/issues",
|
||||
"Source": "https://github.com/YOUR_USERNAME/wikijs-python-sdk",
|
||||
"Documentation": "https://github.com/YOUR_USERNAME/wikijs-python-sdk/docs",
|
||||
}
|
||||
```
|
||||
|
||||
**pyproject.toml** (lines 65-68):
|
||||
```toml
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/YOUR_USERNAME/wikijs-python-sdk"
|
||||
"Bug Reports" = "https://github.com/YOUR_USERNAME/wikijs-python-sdk/issues"
|
||||
Source = "https://github.com/YOUR_USERNAME/wikijs-python-sdk"
|
||||
Documentation = "https://github.com/YOUR_USERNAME/wikijs-python-sdk/docs"
|
||||
```
|
||||
|
||||
**README.md** (lines 6-7):
|
||||
```markdown
|
||||
[](https://github.com/YOUR_USERNAME/wikijs-python-sdk/actions)
|
||||
[](https://codecov.io/gh/YOUR_USERNAME/wikijs-python-sdk)
|
||||
```
|
||||
|
||||
### 2. Verify Package Name Availability
|
||||
Check if `wikijs-python-sdk` is available on PyPI:
|
||||
- Visit https://pypi.org/project/wikijs-python-sdk/
|
||||
- If it returns a 404, the name is available
|
||||
- If needed, update the package name in `setup.py` and `pyproject.toml`
|
||||
|
||||
### 3. Version Management
|
||||
Ensure the version in `wikijs/version.py` reflects the release:
|
||||
```python
|
||||
__version__ = "0.1.0" # Update as needed
|
||||
```
|
||||
|
||||
## PyPI Account Setup
|
||||
|
||||
### 1. Create Accounts
|
||||
Register for both test and production PyPI:
|
||||
- **Test PyPI**: https://test.pypi.org/account/register/
|
||||
- **Production PyPI**: https://pypi.org/account/register/
|
||||
|
||||
### 2. Generate API Tokens
|
||||
For each account, create API tokens:
|
||||
1. Go to Account Settings
|
||||
2. Navigate to "API tokens"
|
||||
3. Click "Add API token"
|
||||
4. Choose scope: "Entire account" (for first upload)
|
||||
5. Save the token securely
|
||||
|
||||
## Publishing Process
|
||||
|
||||
### 1. Install Publishing Tools
|
||||
```bash
|
||||
pip install twine build
|
||||
```
|
||||
|
||||
### 2. Build the Package
|
||||
```bash
|
||||
# Clean previous builds
|
||||
rm -rf dist/ build/ *.egg-info/
|
||||
|
||||
# Build the package
|
||||
python -m build
|
||||
```
|
||||
|
||||
### 3. Validate the Package
|
||||
```bash
|
||||
# Check package for common issues
|
||||
twine check dist/*
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
Checking dist/wikijs_python_sdk-0.1.0-py3-none-any.whl: PASSED
|
||||
Checking dist/wikijs_python_sdk-0.1.0.tar.gz: PASSED
|
||||
```
|
||||
|
||||
### 4. Test Upload (Recommended)
|
||||
Always test on Test PyPI first:
|
||||
|
||||
```bash
|
||||
# Upload to Test PyPI
|
||||
twine upload --repository testpypi dist/*
|
||||
```
|
||||
|
||||
When prompted:
|
||||
- **Username**: `__token__`
|
||||
- **Password**: Your Test PyPI API token
|
||||
|
||||
### 5. Test Installation
|
||||
```bash
|
||||
# Test installation from Test PyPI
|
||||
pip install --index-url https://test.pypi.org/simple/ wikijs-python-sdk
|
||||
|
||||
# Test basic functionality
|
||||
python -c "from wikijs import WikiJSClient; print('Import successful')"
|
||||
```
|
||||
|
||||
### 6. Production Upload
|
||||
Once testing is successful:
|
||||
|
||||
```bash
|
||||
# Upload to production PyPI
|
||||
twine upload dist/*
|
||||
```
|
||||
|
||||
When prompted:
|
||||
- **Username**: `__token__`
|
||||
- **Password**: Your Production PyPI API token
|
||||
|
||||
### 7. Verify Production Installation
|
||||
```bash
|
||||
# Install from PyPI
|
||||
pip install wikijs-python-sdk
|
||||
|
||||
# Verify installation
|
||||
python -c "from wikijs import WikiJSClient; print('Production install successful')"
|
||||
```
|
||||
|
||||
## Post-Publishing Tasks
|
||||
|
||||
### 1. Update Documentation
|
||||
- Update README.md installation instructions
|
||||
- Remove "Coming soon" notes
|
||||
- Add PyPI badge if desired
|
||||
|
||||
### 2. Create GitHub Release
|
||||
1. Go to your GitHub repository
|
||||
2. Click "Releases" → "Create a new release"
|
||||
3. Tag version: `v0.1.0`
|
||||
4. Release title: `v0.1.0 - MVP Release`
|
||||
5. Copy changelog content as description
|
||||
6. Attach built files from `dist/`
|
||||
|
||||
### 3. Announce Release
|
||||
- Update project status in README.md
|
||||
- Consider posting to relevant communities
|
||||
- Update project documentation
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**"Package already exists"**
|
||||
- The package name is taken
|
||||
- Update package name in configuration files
|
||||
- Or contact PyPI if you believe you own the name
|
||||
|
||||
**"Invalid authentication credentials"**
|
||||
- Verify you're using `__token__` as username
|
||||
- Check that the API token is correct and has proper scope
|
||||
- Ensure the token hasn't expired
|
||||
|
||||
**"File already exists"**
|
||||
- You're trying to upload the same version twice
|
||||
- Increment the version number in `wikijs/version.py`
|
||||
- Rebuild the package
|
||||
|
||||
**Package validation errors**
|
||||
- Run `twine check dist/*` for detailed error messages
|
||||
- Common issues: missing README, invalid metadata
|
||||
- Fix issues and rebuild
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **PyPI Help**: https://pypi.org/help/
|
||||
- **Packaging Guide**: https://packaging.python.org/
|
||||
- **Twine Documentation**: https://twine.readthedocs.io/
|
||||
|
||||
## Automated Publishing (Future)
|
||||
|
||||
Consider setting up GitHub Actions for automated publishing:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/publish.yml
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install build twine
|
||||
- name: Build package
|
||||
run: python -m build
|
||||
- name: Publish to PyPI
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||
run: twine upload dist/*
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Never commit API tokens to version control
|
||||
- Use repository secrets for automated publishing
|
||||
- Regularly rotate API tokens
|
||||
- Use scoped tokens when possible
|
||||
- Monitor package downloads for suspicious activity
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Once published, users can install with `pip install wikijs-python-sdk`
|
||||
568
experiment.py
568
experiment.py
@@ -1,568 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Wiki.js Python SDK Experimentation Script
|
||||
|
||||
This interactive script lets you experiment with the SDK features in a safe,
|
||||
mocked environment. Perfect for learning how the SDK works without needing
|
||||
a real Wiki.js instance.
|
||||
|
||||
Usage:
|
||||
python experiment.py
|
||||
|
||||
The script will guide you through different SDK features and let you
|
||||
try them out interactively.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
# Import SDK components
|
||||
from wikijs import WikiJSClient
|
||||
from wikijs.models import PageCreate, PageUpdate, Page
|
||||
from wikijs.auth import APIKeyAuth, JWTAuth, NoAuth
|
||||
from wikijs.exceptions import APIError, ValidationError, NotFoundError
|
||||
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for pretty output."""
|
||||
HEADER = '\033[95m'
|
||||
BLUE = '\033[94m'
|
||||
CYAN = '\033[96m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
RED = '\033[91m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
END = '\033[0m'
|
||||
|
||||
|
||||
def print_header(text):
|
||||
"""Print a colored header."""
|
||||
print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.END}")
|
||||
print(f"{Colors.HEADER}{Colors.BOLD}{text.center(60)}{Colors.END}")
|
||||
print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.END}\n")
|
||||
|
||||
|
||||
def print_success(text):
|
||||
"""Print success message."""
|
||||
print(f"{Colors.GREEN}✅ {text}{Colors.END}")
|
||||
|
||||
|
||||
def print_info(text):
|
||||
"""Print info message."""
|
||||
print(f"{Colors.CYAN}ℹ️ {text}{Colors.END}")
|
||||
|
||||
|
||||
def print_warning(text):
|
||||
"""Print warning message."""
|
||||
print(f"{Colors.YELLOW}⚠️ {text}{Colors.END}")
|
||||
|
||||
|
||||
def print_error(text):
|
||||
"""Print error message."""
|
||||
print(f"{Colors.RED}❌ {text}{Colors.END}")
|
||||
|
||||
|
||||
def print_code(code):
|
||||
"""Print code snippet."""
|
||||
print(f"{Colors.BLUE}{code}{Colors.END}")
|
||||
|
||||
|
||||
def wait_for_enter(prompt="Press Enter to continue..."):
|
||||
"""Wait for user input."""
|
||||
input(f"\n{Colors.YELLOW}{prompt}{Colors.END}")
|
||||
|
||||
|
||||
def setup_mock_session():
|
||||
"""Set up a mock session for API calls."""
|
||||
mock_session = Mock()
|
||||
|
||||
# Sample pages data
|
||||
sample_pages = [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Welcome to Wiki.js",
|
||||
"path": "home",
|
||||
"content": "# Welcome!\n\nThis is your wiki home page.",
|
||||
"created_at": "2023-01-01T00:00:00Z",
|
||||
"updated_at": "2023-01-01T12:00:00Z",
|
||||
"is_published": True,
|
||||
"tags": ["welcome", "home"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Getting Started Guide",
|
||||
"path": "getting-started",
|
||||
"content": "# Getting Started\n\nLearn how to use this wiki effectively.",
|
||||
"created_at": "2023-01-02T00:00:00Z",
|
||||
"updated_at": "2023-01-02T10:00:00Z",
|
||||
"is_published": True,
|
||||
"tags": ["guide", "tutorial"]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "API Documentation",
|
||||
"path": "api-docs",
|
||||
"content": "# API Documentation\n\nComplete API reference.",
|
||||
"created_at": "2023-01-03T00:00:00Z",
|
||||
"updated_at": "2023-01-03T14:00:00Z",
|
||||
"is_published": False,
|
||||
"tags": ["api", "documentation"]
|
||||
}
|
||||
]
|
||||
|
||||
def mock_request(method, url, **kwargs):
|
||||
"""Mock HTTP request handler."""
|
||||
response = Mock()
|
||||
response.ok = True
|
||||
response.status_code = 200
|
||||
|
||||
# Simulate different API endpoints
|
||||
if "pages" in url and method.upper() == "GET":
|
||||
if url.endswith("/pages"):
|
||||
# List pages
|
||||
response.json.return_value = {"data": {"pages": sample_pages}}
|
||||
else:
|
||||
# Get specific page
|
||||
page_id = int(url.split("/")[-1]) if url.split("/")[-1].isdigit() else 1
|
||||
page = next((p for p in sample_pages if p["id"] == page_id), sample_pages[0])
|
||||
response.json.return_value = page
|
||||
|
||||
elif "pages" in url and method.upper() == "POST":
|
||||
# Create page
|
||||
new_page = {
|
||||
"id": len(sample_pages) + 1,
|
||||
"title": kwargs.get("json", {}).get("title", "New Page"),
|
||||
"path": kwargs.get("json", {}).get("path", "new-page"),
|
||||
"content": kwargs.get("json", {}).get("content", ""),
|
||||
"created_at": datetime.now().isoformat() + "Z",
|
||||
"updated_at": datetime.now().isoformat() + "Z",
|
||||
"is_published": kwargs.get("json", {}).get("is_published", True),
|
||||
"tags": kwargs.get("json", {}).get("tags", [])
|
||||
}
|
||||
sample_pages.append(new_page)
|
||||
response.json.return_value = new_page
|
||||
response.status_code = 201
|
||||
|
||||
elif "pages" in url and method.upper() == "PUT":
|
||||
# Update page
|
||||
page_id = int(url.split("/")[-1]) if url.split("/")[-1].isdigit() else 1
|
||||
page = next((p for p in sample_pages if p["id"] == page_id), sample_pages[0])
|
||||
|
||||
# Update fields from request
|
||||
update_data = kwargs.get("json", {})
|
||||
for key, value in update_data.items():
|
||||
if key in page:
|
||||
page[key] = value
|
||||
page["updated_at"] = datetime.now().isoformat() + "Z"
|
||||
|
||||
response.json.return_value = page
|
||||
|
||||
elif "pages" in url and method.upper() == "DELETE":
|
||||
# Delete page
|
||||
page_id = int(url.split("/")[-1]) if url.split("/")[-1].isdigit() else 1
|
||||
sample_pages[:] = [p for p in sample_pages if p["id"] != page_id]
|
||||
response.json.return_value = {"success": True}
|
||||
response.status_code = 204
|
||||
|
||||
else:
|
||||
# Default response
|
||||
response.json.return_value = {"message": "Success"}
|
||||
|
||||
return response
|
||||
|
||||
mock_session.request.side_effect = mock_request
|
||||
return mock_session
|
||||
|
||||
|
||||
def experiment_client_setup():
|
||||
"""Experiment with client setup."""
|
||||
print_header("🔧 CLIENT SETUP EXPERIMENT")
|
||||
|
||||
print_info("Let's create different types of Wiki.js clients!")
|
||||
|
||||
print("\n1. Creating a client with API key authentication:")
|
||||
print_code("client = WikiJSClient('https://wiki.example.com', auth='your-api-key')")
|
||||
|
||||
try:
|
||||
client = WikiJSClient('https://wiki.example.com', auth='demo-api-key-12345')
|
||||
print_success(f"Client created! Base URL: {client.base_url}")
|
||||
print_info(f"Auth type: {type(client._auth_handler).__name__}")
|
||||
except Exception as e:
|
||||
print_error(f"Error creating client: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n2. Creating a client with JWT authentication:")
|
||||
print_code("jwt_token = 'eyJ0eXAiOiJKV1Q...'")
|
||||
print_code("jwt_auth = JWTAuth(jwt_token)")
|
||||
print_code("client = WikiJSClient('https://wiki.example.com', auth=jwt_auth)")
|
||||
|
||||
try:
|
||||
jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
|
||||
jwt_auth = JWTAuth(jwt_token)
|
||||
jwt_client = WikiJSClient('https://wiki.example.com', auth=jwt_auth)
|
||||
print_success("JWT client created successfully!")
|
||||
print_info(f"Token preview: {jwt_auth.token_preview}")
|
||||
except Exception as e:
|
||||
print_error(f"Error creating JWT client: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n3. URL normalization demo:")
|
||||
test_urls = [
|
||||
"wiki.example.com",
|
||||
"https://wiki.example.com/",
|
||||
"http://localhost:3000///",
|
||||
"wiki.company.internal:8080"
|
||||
]
|
||||
|
||||
for url in test_urls:
|
||||
try:
|
||||
client = WikiJSClient(url, auth='test-key')
|
||||
print_success(f"'{url}' → '{client.base_url}'")
|
||||
except Exception as e:
|
||||
print_error(f"'{url}' → Error: {e}")
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def experiment_data_models():
|
||||
"""Experiment with data models."""
|
||||
print_header("📋 DATA MODELS EXPERIMENT")
|
||||
|
||||
print_info("Let's create and manipulate Wiki.js data models!")
|
||||
|
||||
print("\n1. Creating a new page:")
|
||||
print_code("""
|
||||
page_data = PageCreate(
|
||||
title="My Awesome Page",
|
||||
path="awesome-page",
|
||||
content="# Welcome\\n\\nThis is **awesome** content!",
|
||||
tags=["awesome", "demo"],
|
||||
is_published=True
|
||||
)""")
|
||||
|
||||
try:
|
||||
page_data = PageCreate(
|
||||
title="My Awesome Page",
|
||||
path="awesome-page",
|
||||
content="# Welcome\n\nThis is **awesome** content!",
|
||||
tags=["awesome", "demo"],
|
||||
is_published=True
|
||||
)
|
||||
print_success("PageCreate model created!")
|
||||
print_info(f"Title: {page_data.title}")
|
||||
print_info(f"Path: {page_data.path}")
|
||||
print_info(f"Tags: {page_data.tags}")
|
||||
except Exception as e:
|
||||
print_error(f"Error creating page model: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n2. Model serialization:")
|
||||
print_code("page_dict = page_data.to_dict()")
|
||||
print_code("page_json = page_data.to_json()")
|
||||
|
||||
try:
|
||||
page_dict = page_data.to_dict()
|
||||
page_json = page_data.to_json()
|
||||
|
||||
print_success("Serialization successful!")
|
||||
print_info("Dictionary format:")
|
||||
print(json.dumps(page_dict, indent=2))
|
||||
print_info("\nJSON format:")
|
||||
print(page_json)
|
||||
except Exception as e:
|
||||
print_error(f"Serialization error: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n3. Creating update data:")
|
||||
print_code("""
|
||||
update_data = PageUpdate(
|
||||
title="Updated Awesome Page",
|
||||
content="# Updated Content\\n\\nThis content has been updated!",
|
||||
tags=["awesome", "demo", "updated"]
|
||||
)""")
|
||||
|
||||
try:
|
||||
update_data = PageUpdate(
|
||||
title="Updated Awesome Page",
|
||||
content="# Updated Content\n\nThis content has been updated!",
|
||||
tags=["awesome", "demo", "updated"]
|
||||
)
|
||||
print_success("PageUpdate model created!")
|
||||
print_info(f"New title: {update_data.title}")
|
||||
print_info(f"New tags: {update_data.tags}")
|
||||
except Exception as e:
|
||||
print_error(f"Error creating update model: {e}")
|
||||
|
||||
return page_data, update_data
|
||||
|
||||
|
||||
@patch('wikijs.client.requests.Session')
|
||||
def experiment_api_operations(mock_session_class, client, page_data, update_data):
|
||||
"""Experiment with API operations."""
|
||||
print_header("🌐 API OPERATIONS EXPERIMENT")
|
||||
|
||||
# Set up mock session
|
||||
mock_session = setup_mock_session()
|
||||
mock_session_class.return_value = mock_session
|
||||
|
||||
print_info("Let's try different API operations with mocked responses!")
|
||||
|
||||
print("\n1. Listing all pages:")
|
||||
print_code("pages = client.pages.list()")
|
||||
|
||||
try:
|
||||
pages = client.pages.list()
|
||||
print_success(f"Found {len(pages)} pages!")
|
||||
for i, page in enumerate(pages[:3], 1):
|
||||
print_info(f"{i}. {page.title} ({page.path}) - {len(page.tags)} tags")
|
||||
except Exception as e:
|
||||
print_error(f"Error listing pages: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n2. Getting a specific page:")
|
||||
print_code("page = client.pages.get(1)")
|
||||
|
||||
try:
|
||||
page = client.pages.get(1)
|
||||
print_success("Page retrieved!")
|
||||
print_info(f"Title: {page.title}")
|
||||
print_info(f"Path: {page.path}")
|
||||
print_info(f"Published: {page.is_published}")
|
||||
print_info(f"Content preview: {page.content[:50]}...")
|
||||
except Exception as e:
|
||||
print_error(f"Error getting page: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n3. Creating a new page:")
|
||||
print_code("new_page = client.pages.create(page_data)")
|
||||
|
||||
try:
|
||||
new_page = client.pages.create(page_data)
|
||||
print_success("Page created!")
|
||||
print_info(f"New page ID: {new_page.id}")
|
||||
print_info(f"Title: {new_page.title}")
|
||||
print_info(f"Created at: {new_page.created_at}")
|
||||
except Exception as e:
|
||||
print_error(f"Error creating page: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n4. Updating a page:")
|
||||
print_code("updated_page = client.pages.update(1, update_data)")
|
||||
|
||||
try:
|
||||
updated_page = client.pages.update(1, update_data)
|
||||
print_success("Page updated!")
|
||||
print_info(f"Updated title: {updated_page.title}")
|
||||
print_info(f"Updated at: {updated_page.updated_at}")
|
||||
except Exception as e:
|
||||
print_error(f"Error updating page: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n5. Searching pages:")
|
||||
print_code("search_results = client.pages.search('guide')")
|
||||
|
||||
try:
|
||||
search_results = client.pages.search('guide')
|
||||
print_success(f"Found {len(search_results)} matching pages!")
|
||||
for result in search_results:
|
||||
print_info(f"• {result.title} - {result.path}")
|
||||
except Exception as e:
|
||||
print_error(f"Error searching pages: {e}")
|
||||
|
||||
|
||||
def experiment_error_handling():
|
||||
"""Experiment with error handling."""
|
||||
print_header("⚠️ ERROR HANDLING EXPERIMENT")
|
||||
|
||||
print_info("Let's see how the SDK handles different types of errors!")
|
||||
|
||||
print("\n1. Validation errors:")
|
||||
print_code("""
|
||||
try:
|
||||
invalid_page = PageCreate(title="", path="", content="")
|
||||
except ValidationError as e:
|
||||
print(f"Validation error: {e}")
|
||||
""")
|
||||
|
||||
try:
|
||||
invalid_page = PageCreate(title="", path="", content="")
|
||||
print_warning("Expected validation error, but none occurred!")
|
||||
except ValidationError as e:
|
||||
print_success(f"Caught validation error: {e}")
|
||||
except Exception as e:
|
||||
print_error(f"Unexpected error: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n2. Authentication errors:")
|
||||
print_code("""
|
||||
try:
|
||||
bad_auth = APIKeyAuth("")
|
||||
except ValidationError as e:
|
||||
print(f"Auth error: {e}")
|
||||
""")
|
||||
|
||||
try:
|
||||
bad_auth = APIKeyAuth("")
|
||||
print_warning("Expected authentication error, but none occurred!")
|
||||
except ValidationError as e:
|
||||
print_success(f"Caught auth error: {e}")
|
||||
except Exception as e:
|
||||
print_error(f"Unexpected error: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n3. URL validation errors:")
|
||||
print_code("""
|
||||
try:
|
||||
from wikijs.utils.helpers import normalize_url
|
||||
normalize_url("")
|
||||
except ValidationError as e:
|
||||
print(f"URL error: {e}")
|
||||
""")
|
||||
|
||||
try:
|
||||
from wikijs.utils.helpers import normalize_url
|
||||
normalize_url("")
|
||||
print_warning("Expected URL validation error!")
|
||||
except ValidationError as e:
|
||||
print_success(f"Caught URL error: {e}")
|
||||
except Exception as e:
|
||||
print_error(f"Unexpected error: {e}")
|
||||
|
||||
|
||||
def experiment_utilities():
|
||||
"""Experiment with utility functions."""
|
||||
print_header("🛠️ UTILITIES EXPERIMENT")
|
||||
|
||||
print_info("Let's try out the SDK's utility functions!")
|
||||
|
||||
from wikijs.utils.helpers import (
|
||||
normalize_url, sanitize_path, chunk_list,
|
||||
safe_get, build_api_url
|
||||
)
|
||||
|
||||
print("\n1. URL normalization:")
|
||||
test_urls = [
|
||||
"wiki.example.com",
|
||||
"https://wiki.example.com/",
|
||||
"localhost:3000",
|
||||
"wiki.company.internal:8080/"
|
||||
]
|
||||
|
||||
for url in test_urls:
|
||||
try:
|
||||
normalized = normalize_url(url)
|
||||
print_success(f"'{url}' → '{normalized}'")
|
||||
except Exception as e:
|
||||
print_error(f"'{url}' → Error: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n2. Path sanitization:")
|
||||
test_paths = [
|
||||
"hello world",
|
||||
"/my/wiki/page/",
|
||||
"special-chars!@#$",
|
||||
" multiple spaces "
|
||||
]
|
||||
|
||||
for path in test_paths:
|
||||
try:
|
||||
sanitized = sanitize_path(path)
|
||||
print_success(f"'{path}' → '{sanitized}'")
|
||||
except Exception as e:
|
||||
print_error(f"'{path}' → Error: {e}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n3. List chunking:")
|
||||
test_list = list(range(1, 13)) # [1, 2, 3, ..., 12]
|
||||
chunk_sizes = [3, 4, 5]
|
||||
|
||||
for size in chunk_sizes:
|
||||
chunks = chunk_list(test_list, size)
|
||||
print_success(f"Chunks of {size}: {chunks}")
|
||||
|
||||
wait_for_enter()
|
||||
|
||||
print("\n4. Safe dictionary access:")
|
||||
test_data = {
|
||||
"user": {
|
||||
"profile": {
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"theme": "dark",
|
||||
"notifications": True
|
||||
}
|
||||
}
|
||||
|
||||
test_keys = [
|
||||
"user.profile.name",
|
||||
"user.profile.email",
|
||||
"settings.theme",
|
||||
"user.missing.key",
|
||||
"nonexistent"
|
||||
]
|
||||
|
||||
for key in test_keys:
|
||||
value = safe_get(test_data, key, "NOT_FOUND")
|
||||
print_success(f"'{key}' → {value}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main experimentation function."""
|
||||
print_header("🧪 WIKI.JS SDK EXPERIMENTATION LAB")
|
||||
|
||||
print(f"{Colors.CYAN}Welcome to the Wiki.js Python SDK Experiment Lab!{Colors.END}")
|
||||
print(f"{Colors.CYAN}Here you can safely try out all the SDK features with mocked data.{Colors.END}")
|
||||
|
||||
wait_for_enter("Ready to start experimenting?")
|
||||
|
||||
# Experiment with different features
|
||||
client = experiment_client_setup()
|
||||
page_data, update_data = experiment_data_models()
|
||||
experiment_api_operations(client, page_data, update_data)
|
||||
experiment_error_handling()
|
||||
experiment_utilities()
|
||||
|
||||
# Final summary
|
||||
print_header("🎉 EXPERIMENT COMPLETE")
|
||||
print_success("Congratulations! You've experimented with:")
|
||||
print_info("✨ Client setup and authentication")
|
||||
print_info("✨ Data models and serialization")
|
||||
print_info("✨ API operations (mocked)")
|
||||
print_info("✨ Error handling")
|
||||
print_info("✨ Utility functions")
|
||||
|
||||
print(f"\n{Colors.YELLOW}💡 Next steps:{Colors.END}")
|
||||
print(f"{Colors.CYAN}1. Check out the examples/ directory for real-world usage{Colors.END}")
|
||||
print(f"{Colors.CYAN}2. Read the docs/user_guide.md for detailed documentation{Colors.END}")
|
||||
print(f"{Colors.CYAN}3. Try connecting to a real Wiki.js instance{Colors.END}")
|
||||
|
||||
print(f"\n{Colors.GREEN}Happy coding with Wiki.js SDK! 🚀{Colors.END}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n\n{Colors.YELLOW}Experiment interrupted. Goodbye! 👋{Colors.END}")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print_error(f"Unexpected error: {e}")
|
||||
sys.exit(1)
|
||||
@@ -5,14 +5,13 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
name = "wikijs-python-sdk"
|
||||
description = "A professional Python SDK for Wiki.js API integration"
|
||||
authors = [{name = "Wiki.js SDK Contributors"}]
|
||||
license = {text = "MIT"}
|
||||
authors = [{name = "Wiki.js SDK Contributors", email = "contact@wikijs-sdk.dev"}]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
@@ -31,6 +30,7 @@ dependencies = [
|
||||
"typing-extensions>=4.0.0",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
keywords = ["wiki", "wikijs", "api", "sdk", "client", "http", "rest"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
|
||||
2
setup.py
2
setup.py
@@ -37,7 +37,7 @@ setup(
|
||||
name="wikijs-python-sdk",
|
||||
version=get_version(),
|
||||
author="Wiki.js SDK Contributors",
|
||||
author_email="",
|
||||
author_email="contact@wikijs-sdk.dev",
|
||||
description="A professional Python SDK for Wiki.js API integration",
|
||||
long_description=get_long_description(),
|
||||
long_description_content_type="text/markdown",
|
||||
|
||||
341
test_runner.py
341
test_runner.py
@@ -1,341 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test Runner for Wiki.js Python SDK
|
||||
|
||||
This file provides a simple way to test the SDK functionality without needing
|
||||
a real Wiki.js instance. It uses mocked responses to simulate API interactions.
|
||||
|
||||
Usage:
|
||||
python test_runner.py
|
||||
|
||||
Or import and run specific tests:
|
||||
from test_runner import run_all_tests, test_client_creation
|
||||
run_all_tests()
|
||||
"""
|
||||
|
||||
import json
|
||||
from unittest.mock import Mock, patch
|
||||
from datetime import datetime
|
||||
|
||||
# Import SDK components
|
||||
from wikijs import WikiJSClient
|
||||
from wikijs.models import PageCreate, PageUpdate
|
||||
from wikijs.auth import APIKeyAuth, JWTAuth
|
||||
from wikijs.exceptions import APIError, ValidationError
|
||||
|
||||
|
||||
def test_client_creation():
|
||||
"""Test basic client creation and configuration."""
|
||||
print("🔧 Testing client creation...")
|
||||
|
||||
# Test with API key
|
||||
client = WikiJSClient("https://wiki.example.com", auth="test-api-key")
|
||||
assert client.base_url == "https://wiki.example.com"
|
||||
assert isinstance(client._auth_handler, APIKeyAuth)
|
||||
print(" ✅ API key authentication works")
|
||||
|
||||
# Test with JWT
|
||||
jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
|
||||
jwt_auth = JWTAuth(jwt_token)
|
||||
client_jwt = WikiJSClient("https://wiki.example.com", auth=jwt_auth)
|
||||
assert isinstance(client_jwt._auth_handler, JWTAuth)
|
||||
print(" ✅ JWT authentication works")
|
||||
|
||||
# Test URL normalization
|
||||
client_normalized = WikiJSClient("wiki.example.com/", auth="test-key")
|
||||
assert client_normalized.base_url == "https://wiki.example.com"
|
||||
print(" ✅ URL normalization works")
|
||||
|
||||
print("✅ Client creation tests passed!\n")
|
||||
return True
|
||||
|
||||
|
||||
def test_models():
|
||||
"""Test data model functionality."""
|
||||
print("📋 Testing data models...")
|
||||
|
||||
# Test PageCreate model
|
||||
page_data = PageCreate(
|
||||
title="Test Page",
|
||||
path="test-page",
|
||||
content="# Hello World\n\nThis is a test page.",
|
||||
tags=["test", "example"],
|
||||
)
|
||||
|
||||
assert page_data.title == "Test Page"
|
||||
assert page_data.path == "test-page"
|
||||
assert "test" in page_data.tags
|
||||
print(" ✅ PageCreate model works")
|
||||
|
||||
# Test model serialization
|
||||
page_dict = page_data.to_dict()
|
||||
assert page_dict["title"] == "Test Page"
|
||||
assert isinstance(page_dict, dict)
|
||||
print(" ✅ Model serialization works")
|
||||
|
||||
# Test JSON serialization
|
||||
page_json = page_data.to_json()
|
||||
parsed = json.loads(page_json)
|
||||
assert parsed["title"] == "Test Page"
|
||||
print(" ✅ JSON serialization works")
|
||||
|
||||
# Test PageUpdate model
|
||||
update_data = PageUpdate(title="Updated Title", content="Updated content")
|
||||
assert update_data.title == "Updated Title"
|
||||
print(" ✅ PageUpdate model works")
|
||||
|
||||
print("✅ Data model tests passed!\n")
|
||||
return True
|
||||
|
||||
|
||||
@patch("wikijs.client.requests.Session")
|
||||
def test_mocked_api_calls(mock_session_class):
|
||||
"""Test API calls with mocked responses."""
|
||||
print("🌐 Testing mocked API calls...")
|
||||
|
||||
# Setup mock session
|
||||
mock_session = Mock()
|
||||
mock_session_class.return_value = mock_session
|
||||
|
||||
# Mock successful response for list pages
|
||||
mock_response = Mock()
|
||||
mock_response.ok = True
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": {
|
||||
"pages": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Home Page",
|
||||
"path": "home",
|
||||
"content": "Welcome to the wiki!",
|
||||
"created_at": "2023-01-01T00:00:00Z",
|
||||
"updated_at": "2023-01-01T12:00:00Z",
|
||||
"is_published": True,
|
||||
"tags": ["welcome"],
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Getting Started",
|
||||
"path": "getting-started",
|
||||
"content": "How to use this wiki.",
|
||||
"created_at": "2023-01-02T00:00:00Z",
|
||||
"updated_at": "2023-01-02T10:00:00Z",
|
||||
"is_published": True,
|
||||
"tags": ["guide"],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
mock_session.request.return_value = mock_response
|
||||
|
||||
# Test client with mocked session
|
||||
client = WikiJSClient("https://wiki.example.com", auth="test-key")
|
||||
|
||||
# Test list pages (this would normally make an HTTP request)
|
||||
try:
|
||||
pages = client.pages.list()
|
||||
print(" ✅ Pages list method called successfully")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ List pages method exists but may need actual implementation: {e}")
|
||||
|
||||
# Test individual page operations
|
||||
try:
|
||||
# Mock response for creating a page
|
||||
mock_response.json.return_value = {
|
||||
"id": 3,
|
||||
"title": "New Page",
|
||||
"path": "new-page",
|
||||
"content": "This is new content",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
"is_published": True,
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
page_data = PageCreate(
|
||||
title="New Page", path="new-page", content="This is new content"
|
||||
)
|
||||
|
||||
new_page = client.pages.create(page_data)
|
||||
print(" ✅ Page creation method called successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(
|
||||
f" ⚠️ Page creation method exists but may need implementation details: {e}"
|
||||
)
|
||||
|
||||
print("✅ Mocked API call tests completed!\n")
|
||||
return True
|
||||
|
||||
|
||||
def test_authentication():
|
||||
"""Test different authentication methods."""
|
||||
print("🔐 Testing authentication methods...")
|
||||
|
||||
# Test API Key Authentication
|
||||
api_auth = APIKeyAuth("test-api-key-12345")
|
||||
headers = api_auth.get_headers()
|
||||
assert "Authorization" in headers
|
||||
assert "Bearer test-api-key-12345" in headers["Authorization"]
|
||||
assert api_auth.is_valid() == True
|
||||
print(" ✅ API Key authentication works")
|
||||
|
||||
# Test JWT Authentication
|
||||
jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
|
||||
jwt_auth = JWTAuth(jwt_token)
|
||||
jwt_headers = jwt_auth.get_headers()
|
||||
assert "Authorization" in jwt_headers
|
||||
assert jwt_token in jwt_headers["Authorization"]
|
||||
print(" ✅ JWT authentication works")
|
||||
|
||||
# Test authentication validation
|
||||
try:
|
||||
api_auth.validate_credentials()
|
||||
print(" ✅ Authentication validation works")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Authentication validation: {e}")
|
||||
|
||||
print("✅ Authentication tests passed!\n")
|
||||
return True
|
||||
|
||||
|
||||
def test_exceptions():
|
||||
"""Test exception handling."""
|
||||
print("⚠️ Testing exception handling...")
|
||||
|
||||
# Test validation errors
|
||||
try:
|
||||
PageCreate(title="", path="invalid path", content="test")
|
||||
print(" ❌ Should have raised validation error")
|
||||
except ValidationError:
|
||||
print(" ✅ Validation error handling works")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Got different exception: {e}")
|
||||
|
||||
# Test API error creation
|
||||
try:
|
||||
from wikijs.exceptions import create_api_error
|
||||
|
||||
error = create_api_error(404, "Not found", None)
|
||||
assert "Not found" in str(error)
|
||||
print(" ✅ API error creation works")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ API error creation: {e}")
|
||||
|
||||
print("✅ Exception handling tests completed!\n")
|
||||
return True
|
||||
|
||||
|
||||
def test_utilities():
|
||||
"""Test utility functions."""
|
||||
print("🛠️ Testing utility functions...")
|
||||
|
||||
from wikijs.utils.helpers import normalize_url, sanitize_path, chunk_list
|
||||
|
||||
# Test URL normalization
|
||||
normalized = normalize_url("wiki.example.com/")
|
||||
assert normalized == "https://wiki.example.com"
|
||||
print(" ✅ URL normalization works")
|
||||
|
||||
# Test path sanitization
|
||||
try:
|
||||
sanitized = sanitize_path("hello world/test")
|
||||
assert "hello-world" in sanitized
|
||||
print(" ✅ Path sanitization works")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Path sanitization: {e}")
|
||||
|
||||
# Test list chunking
|
||||
chunks = chunk_list([1, 2, 3, 4, 5], 2)
|
||||
assert len(chunks) == 3
|
||||
assert chunks[0] == [1, 2]
|
||||
print(" ✅ List chunking works")
|
||||
|
||||
print("✅ Utility function tests passed!\n")
|
||||
return True
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all test functions."""
|
||||
print("🚀 Running Wiki.js Python SDK Tests")
|
||||
print("=" * 50)
|
||||
|
||||
tests = [
|
||||
test_client_creation,
|
||||
test_models,
|
||||
test_authentication,
|
||||
test_mocked_api_calls,
|
||||
test_exceptions,
|
||||
test_utilities,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test_func in tests:
|
||||
try:
|
||||
if test_func():
|
||||
passed += 1
|
||||
except Exception as e:
|
||||
print(f"❌ {test_func.__name__} failed: {e}\n")
|
||||
|
||||
print("=" * 50)
|
||||
print(f"📊 Test Results: {passed}/{total} tests passed")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All tests passed! The SDK is working correctly.")
|
||||
else:
|
||||
print(f"⚠️ {total - passed} tests had issues. Check output above for details.")
|
||||
|
||||
return passed == total
|
||||
|
||||
|
||||
def demo_usage():
|
||||
"""Demonstrate basic SDK usage."""
|
||||
print("\n" + "=" * 50)
|
||||
print("📖 SDK USAGE DEMO")
|
||||
print("=" * 50)
|
||||
|
||||
print("1. Creating a client:")
|
||||
print(" client = WikiJSClient('https://wiki.example.com', auth='your-api-key')")
|
||||
|
||||
print("\n2. Creating page data:")
|
||||
print(" page_data = PageCreate(")
|
||||
print(" title='My Page',")
|
||||
print(" path='my-page',")
|
||||
print(" content='# Hello\\n\\nThis is my page content!'")
|
||||
print(" )")
|
||||
|
||||
print("\n3. Working with the client:")
|
||||
print(" # List pages")
|
||||
print(" pages = client.pages.list()")
|
||||
print(" ")
|
||||
print(" # Create a page")
|
||||
print(" new_page = client.pages.create(page_data)")
|
||||
print(" ")
|
||||
print(" # Get a specific page")
|
||||
print(" page = client.pages.get(123)")
|
||||
print(" ")
|
||||
print(" # Update a page")
|
||||
print(" update_data = PageUpdate(title='Updated Title')")
|
||||
print(" updated_page = client.pages.update(123, update_data)")
|
||||
|
||||
print("\n4. Error handling:")
|
||||
print(" try:")
|
||||
print(" page = client.pages.get(999)")
|
||||
print(" except NotFoundError:")
|
||||
print(" print('Page not found!')")
|
||||
print(" except APIError as e:")
|
||||
print(" print(f'API error: {e}')")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run all tests
|
||||
success = run_all_tests()
|
||||
|
||||
# Show usage demo
|
||||
demo_usage()
|
||||
|
||||
# Exit with appropriate code
|
||||
exit(0 if success else 1)
|
||||
@@ -82,68 +82,32 @@ class PagesEndpoint(BaseEndpoint):
|
||||
if order_direction not in ["ASC", "DESC"]:
|
||||
raise ValidationError("order_direction must be ASC or DESC")
|
||||
|
||||
# Build GraphQL query
|
||||
# Build GraphQL query using actual Wiki.js schema
|
||||
query = """
|
||||
query($limit: Int, $offset: Int, $search: String, $tags: [String], $locale: String, $authorId: Int, $orderBy: String, $orderDirection: String) {
|
||||
pages(
|
||||
limit: $limit,
|
||||
offset: $offset,
|
||||
search: $search,
|
||||
tags: $tags,
|
||||
locale: $locale,
|
||||
authorId: $authorId,
|
||||
orderBy: $orderBy,
|
||||
orderDirection: $orderDirection
|
||||
) {
|
||||
query {
|
||||
pages {
|
||||
list {
|
||||
id
|
||||
title
|
||||
path
|
||||
content
|
||||
description
|
||||
isPublished
|
||||
isPrivate
|
||||
tags
|
||||
locale
|
||||
authorId
|
||||
authorName
|
||||
authorEmail
|
||||
editor
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Build variables
|
||||
variables = {
|
||||
"orderBy": order_by,
|
||||
"orderDirection": order_direction
|
||||
}
|
||||
|
||||
if limit is not None:
|
||||
variables["limit"] = limit
|
||||
if offset is not None:
|
||||
variables["offset"] = offset
|
||||
if search:
|
||||
variables["search"] = search
|
||||
if tags:
|
||||
variables["tags"] = tags
|
||||
if locale:
|
||||
variables["locale"] = locale
|
||||
if author_id is not None:
|
||||
variables["authorId"] = author_id
|
||||
|
||||
# Make request
|
||||
# Make request (no variables needed for simple list query)
|
||||
response = self._post("/graphql", json_data={
|
||||
"query": query,
|
||||
"variables": variables
|
||||
"query": query
|
||||
})
|
||||
|
||||
# Parse response
|
||||
if "errors" in response:
|
||||
raise APIError(f"GraphQL errors: {response['errors']}")
|
||||
|
||||
pages_data = response.get("data", {}).get("pages", [])
|
||||
pages_data = response.get("data", {}).get("pages", {}).get("list", [])
|
||||
|
||||
# Convert to Page objects
|
||||
pages = []
|
||||
@@ -174,10 +138,11 @@ class PagesEndpoint(BaseEndpoint):
|
||||
if not isinstance(page_id, int) or page_id < 1:
|
||||
raise ValidationError("page_id must be a positive integer")
|
||||
|
||||
# Build GraphQL query
|
||||
# Build GraphQL query using actual Wiki.js schema
|
||||
query = """
|
||||
query($id: Int!) {
|
||||
page(id: $id) {
|
||||
pages {
|
||||
single(id: $id) {
|
||||
id
|
||||
title
|
||||
path
|
||||
@@ -185,7 +150,9 @@ class PagesEndpoint(BaseEndpoint):
|
||||
description
|
||||
isPublished
|
||||
isPrivate
|
||||
tags
|
||||
tags {
|
||||
tag
|
||||
}
|
||||
locale
|
||||
authorId
|
||||
authorName
|
||||
@@ -195,6 +162,7 @@ class PagesEndpoint(BaseEndpoint):
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Make request
|
||||
@@ -207,7 +175,7 @@ class PagesEndpoint(BaseEndpoint):
|
||||
if "errors" in response:
|
||||
raise APIError(f"GraphQL errors: {response['errors']}")
|
||||
|
||||
page_data = response.get("data", {}).get("page")
|
||||
page_data = response.get("data", {}).get("pages", {}).get("single")
|
||||
if not page_data:
|
||||
raise APIError(f"Page with ID {page_id} not found")
|
||||
|
||||
@@ -304,20 +272,18 @@ class PagesEndpoint(BaseEndpoint):
|
||||
elif not isinstance(page_data, PageCreate):
|
||||
raise ValidationError("page_data must be PageCreate object or dict")
|
||||
|
||||
# Build GraphQL mutation
|
||||
# Build GraphQL mutation using actual Wiki.js schema
|
||||
mutation = """
|
||||
mutation($title: String!, $path: String!, $content: String!, $description: String, $isPublished: Boolean, $isPrivate: Boolean, $tags: [String], $locale: String, $editor: String) {
|
||||
createPage(
|
||||
title: $title,
|
||||
path: $path,
|
||||
content: $content,
|
||||
description: $description,
|
||||
isPublished: $isPublished,
|
||||
isPrivate: $isPrivate,
|
||||
tags: $tags,
|
||||
locale: $locale,
|
||||
editor: $editor
|
||||
) {
|
||||
mutation($content: String!, $description: String!, $editor: String!, $isPublished: Boolean!, $isPrivate: Boolean!, $locale: String!, $path: String!, $tags: [String]!, $title: String!) {
|
||||
pages {
|
||||
create(content: $content, description: $description, editor: $editor, isPublished: $isPublished, isPrivate: $isPrivate, locale: $locale, path: $path, tags: $tags, title: $title) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
page {
|
||||
id
|
||||
title
|
||||
path
|
||||
@@ -325,7 +291,9 @@ class PagesEndpoint(BaseEndpoint):
|
||||
description
|
||||
isPublished
|
||||
isPrivate
|
||||
tags
|
||||
tags {
|
||||
tag
|
||||
}
|
||||
locale
|
||||
authorId
|
||||
authorName
|
||||
@@ -335,6 +303,8 @@ class PagesEndpoint(BaseEndpoint):
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Build variables from page data
|
||||
@@ -342,6 +312,7 @@ class PagesEndpoint(BaseEndpoint):
|
||||
"title": page_data.title,
|
||||
"path": page_data.path,
|
||||
"content": page_data.content,
|
||||
"description": page_data.description or f"Created via SDK: {page_data.title}",
|
||||
"isPublished": page_data.is_published,
|
||||
"isPrivate": page_data.is_private,
|
||||
"tags": page_data.tags,
|
||||
@@ -349,9 +320,6 @@ class PagesEndpoint(BaseEndpoint):
|
||||
"editor": page_data.editor
|
||||
}
|
||||
|
||||
if page_data.description is not None:
|
||||
variables["description"] = page_data.description
|
||||
|
||||
# Make request
|
||||
response = self._post("/graphql", json_data={
|
||||
"query": mutation,
|
||||
@@ -362,9 +330,16 @@ class PagesEndpoint(BaseEndpoint):
|
||||
if "errors" in response:
|
||||
raise APIError(f"Failed to create page: {response['errors']}")
|
||||
|
||||
created_page_data = response.get("data", {}).get("createPage")
|
||||
create_result = response.get("data", {}).get("pages", {}).get("create", {})
|
||||
response_result = create_result.get("responseResult", {})
|
||||
|
||||
if not response_result.get("succeeded"):
|
||||
error_msg = response_result.get("message", "Unknown error")
|
||||
raise APIError(f"Page creation failed: {error_msg}")
|
||||
|
||||
created_page_data = create_result.get("page")
|
||||
if not created_page_data:
|
||||
raise APIError("Page creation failed - no data returned")
|
||||
raise APIError("Page creation failed - no page data returned")
|
||||
|
||||
# Convert to Page object
|
||||
try:
|
||||
@@ -613,7 +588,6 @@ class PagesEndpoint(BaseEndpoint):
|
||||
"description": "description",
|
||||
"isPublished": "is_published",
|
||||
"isPrivate": "is_private",
|
||||
"tags": "tags",
|
||||
"locale": "locale",
|
||||
"authorId": "author_id",
|
||||
"authorName": "author_name",
|
||||
@@ -627,8 +601,20 @@ class PagesEndpoint(BaseEndpoint):
|
||||
if api_field in page_data:
|
||||
normalized[model_field] = page_data[api_field]
|
||||
|
||||
# Ensure required fields have defaults
|
||||
if "tags" not in normalized:
|
||||
# Handle tags - convert from Wiki.js format
|
||||
if "tags" in page_data:
|
||||
if isinstance(page_data["tags"], list):
|
||||
# Handle both formats: ["tag1", "tag2"] or [{"tag": "tag1"}, {"tag": "tag2"}]
|
||||
tags = []
|
||||
for tag in page_data["tags"]:
|
||||
if isinstance(tag, dict) and "tag" in tag:
|
||||
tags.append(tag["tag"])
|
||||
elif isinstance(tag, str):
|
||||
tags.append(tag)
|
||||
normalized["tags"] = tags
|
||||
else:
|
||||
normalized["tags"] = []
|
||||
else:
|
||||
normalized["tags"] = []
|
||||
|
||||
return normalized
|
||||
@@ -19,7 +19,7 @@ class Page(TimestampedModel):
|
||||
id: int = Field(..., description="Unique page identifier")
|
||||
title: str = Field(..., description="Page title")
|
||||
path: str = Field(..., description="Page path/slug")
|
||||
content: str = Field(..., description="Page content")
|
||||
content: Optional[str] = Field(None, description="Page content")
|
||||
|
||||
# Optional fields that may be present
|
||||
description: Optional[str] = Field(None, description="Page description")
|
||||
|
||||
21
working_playground.py
Normal file
21
working_playground.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from wikijs import WikiJSClient
|
||||
|
||||
client = WikiJSClient(
|
||||
base_url="https://wikijs.hotserv.cloud",
|
||||
auth="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGkiOjEsImdycCI6MSwiaWF0IjoxNTM4ODM1NTQ1LCJleHAiOjE3ODUzOTMxNDUsImF1ZCI6InVybjp3aWtpLmpzIiwiaXNzIjoidXJuOndpa2kuanMifQ.d1fCZMqS-4gR5TfcMU4CLc_mD-uyYxlUxPbxbqqdIazruKKmBLACkVEumf-RFgEatsuCQjQiU0A6E_IfwFBgqFy1g5W_Ly9st7_5k6JOHfn4shGnCrRv3FBLHOtiRUexURcXNvHxh00oEJ8IPuhmTDSpc1g5ssVeNR9oHwz8V-CIvtmP_S5NIalTVEeOXmSSfyHXK4_sMx8zbBb8tCHNt1tbhZ8Z5N--pqvWZFC_ddYZ8-kMkQo-ni1rP48WLpEngWCij6mAPKhdqLjykmIkZF_hwnfvunG7iIZpFVoUJ3uIc09GkIVa5VdpcBHD4w1rnpouWZP8FuR9aHlAL7sB3Q"
|
||||
)
|
||||
|
||||
print("✅ Client created")
|
||||
print("✅ Connection:", client.test_connection())
|
||||
pages = client.pages.list()
|
||||
print(f"✅ Found {len(pages)} pages")
|
||||
for i, page in enumerate(pages[:5], 1):
|
||||
print(f" {i}. {page.title} (ID: {page.id})")
|
||||
client.close()
|
||||
print("✅ SDK working!")
|
||||
Reference in New Issue
Block a user