# Dida365/TickTick API Client
An unofficial Python client library for the Dida365/TickTick API, supporting both the Chinese (Dida365) and international (TickTick) versions of the service. Built with modern async Python and robust error handling.
This is a package created to facilitate task management automation. It is not affiliated with or endorsed by Dida365 or TickTick.
## API Documentation References
- Dida365 API: https://developer.dida365.com/api#/openapi
- TickTick API: https://developer.ticktick.com/docs#/openapi
## OAuth2 Setup
1. Get your OAuth2 credentials:
- For TickTick: Visit https://developer.ticktick.com/manage
- For Dida365: Visit https://developer.dida365.com/manage
- Click "New App" to create a new application
- After creation, you'll receive your Client ID and Client Secret
2. Configure OAuth2 redirect URL:
- In your Manage App page, click "Edit" of your newly created app
- Add the redirect URL: `http://localhost:8080/callback` at "OAuth redirect URL"
- Save the changes
- Note: If you want to use a different redirect URL, make sure to update it in both:
- The app settings on TickTick/Dida365 developer portal
- Your .env file (see below)
3. Configure your credentials:
```bash
# .env file
DIDA365_CLIENT_ID=your_client_id # From step 1
DIDA365_CLIENT_SECRET=your_client_secret # From step 1
DIDA365_REDIRECT_URI=http://localhost:8080/callback # From step 2
DIDA365_SERVICE_TYPE=ticktick # or dida365
```
## Features
- ✨ Full async support using `httpx`
- 🔒 OAuth2 authentication with automatic token management
- 📝 Type-safe with Pydantic v2 models
- 🌐 Configurable endpoints (Dida365/TickTick)
- 🛡️ Comprehensive error handling
- ⚡ Automatic retry mechanism
- 🔄 Environment file integration
- 📊 State management for tasks and projects
## Installation
```bash
pip install dida365
```
## Quick Start
```python
import asyncio
from datetime import datetime, timezone
from dida365 import Dida365Client, ServiceType, TaskCreate, ProjectCreate, TaskPriority
async def main():
# Initialize client (credentials can also be loaded from .env file)
client = Dida365Client(
client_id="your_client_id", # Optional if in .env
client_secret="your_client_secret", # Optional if in .env
service_type=ServiceType.TICKTICK, # or DIDA365
redirect_uri="http://localhost:8080/callback", # Optional
save_to_env=True # Automatically save credentials and tokens to .env
)
# First-time authentication:
if not client.auth.token:
# This will start a local server at the redirect_uri
# and open your browser for authorization
await client.authenticate()
# Token will be automatically saved to .env if save_to_env=True
# Create a project
project = await client.create_project(
ProjectCreate(
name="My Project",
color="#FF0000"
)
)
# Create a task
task = await client.create_task(
TaskCreate(
project_id=project.id,
title="My new task",
content="Task description",
priority=TaskPriority.HIGH,
start_date=datetime.now(timezone.utc),
is_all_day=False,
time_zone="UTC"
)
)
print(f"Created task: {task.title}")
if __name__ == "__main__":
asyncio.run(main())
```
## CRUD Operations
### Projects
```python
from dida365 import ProjectCreate, ProjectUpdate, ViewMode, ProjectKind, Dida365Client
async def manage_projects(client: Dida365Client):
# Create a project
project = await client.create_project(
ProjectCreate(
name="My Project",
color="#FF0000", # Optional: hex color code
view_mode=ViewMode.KANBAN, # Optional: LIST, KANBAN, TIMELINE
kind=ProjectKind.TASK # Optional: TASK, NOTE
)
)
# Get project we just created
project = await client.get_project(project_id=project.id)
# Get project with all tasks and columns
project_data = await client.get_project_with_data(project_id=project.id)
print(f"Project {project_data.project.name} has {len(project_data.tasks)} tasks")
for column in project_data.columns: # Only present in KANBAN view
print(f"Column: {column.name}")
# Update project
updated_project = await client.update_project(
ProjectUpdate(
id=project.id,
name="Updated Project Name",
color="#00FF00",
view_mode=ViewMode.LIST
)
)
# Delete project
await client.delete_project(project_id=project.id)
# List all projects
projects = await client.get_projects()
for project in projects:
print(f"Project: {project.name} ({project.id})")
```
### Tasks
```python
from datetime import datetime, timezone
from dida365 import TaskCreate, TaskUpdate, TaskPriority, Dida365Client, Project
async def manage_tasks(client: Dida365Client, project: Project):
# Create a task
task = await client.create_task(
TaskCreate(
project_id=project.id, # Required: tasks must belong to a project
title="Complete documentation",
content="Add CRUD examples",
priority=TaskPriority.HIGH, # Enum: NONE, LOW, MEDIUM, HIGH
start_date=datetime.now(timezone.utc),
due_date=datetime.now(timezone.utc),
is_all_day=True,
time_zone="UTC"
)
)
# Read a task
task = await client.get_task(project_id=project.id, task_id=task.id)
# Update a task
updated_task = await client.update_task(
TaskUpdate(
id=task.id,
project_id=task.project_id, # Both id and project_id are required
title="Updated title",
content="Added more details",
priority=TaskPriority.MEDIUM
)
)
# Complete a task
await client.complete_task(project_id=task.project_id, task_id=task.id)
# Delete a task
await client.delete_task(project_id=task.project_id, task_id=task.id)
```
### State Management
The client maintains an internal state of tasks and projects:
```python
# Access cached state
tasks = client.state["tasks"]
projects = client.state["projects"]
tags = client.state["tags"]
# State is automatically updated when you:
# 1. Create new items
# 2. Update existing items
# 3. Delete items
```
## Configuration
The client can be configured through:
1. Environment variables
2. Constructor parameters
3. pyproject.toml settings
### Environment Variables
Create a `.env` file:
```bash
# Required credentials
DIDA365_CLIENT_ID=your_client_id
DIDA365_CLIENT_SECRET=your_client_secret
# Optional configurations
DIDA365_SERVICE_TYPE=ticktick # or dida365 (default)
DIDA365_ACCESS_TOKEN=your_token # Will be saved automatically after auth
DIDA365_BASE_URL=custom_url # Optional: custom API endpoint
DIDA365_LOG_LEVEL=INFO # Optional: DEBUG, INFO, WARNING, ERROR
```
You can also use a custom `.env` file location:
```python
from dotenv import load_dotenv
load_dotenv("/path/to/your/.env")
client = Dida365Client() # Will load from the specified .env file
```
### Request Timeouts
Configure request timeouts in `pyproject.toml`:
```toml
[tool.dida365.request_timeout]
connect = 10.0 # Connection timeout
read = 30.0 # Read timeout
write = 30.0 # Write timeout
pool = 5.0 # Pool timeout
```
### Logging Configuration
```toml
[tool.dida365]
log_level = "INFO"
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"
log_file = "" # Set to a path to enable file logging
debug = false
```
## Error Handling
The library provides detailed error handling:
```python
from dida365.exceptions import (
ApiError,
AuthenticationError,
NotFoundError,
RateLimitError,
ValidationError
)
try:
task = await client.get_task("project_id", "task_id")
except NotFoundError:
print("Task not found")
except AuthenticationError:
print("Authentication failed - token may have expired")
except RateLimitError as e:
print(f"Rate limit exceeded. Retry after {e.retry_after} seconds")
except ValidationError as e:
print(f"Invalid data: {e}")
except ApiError as e:
print(f"API error: {e.status_code} - {e.message}")
```
## Author
Carter Yifeng Cheng ([@cyfine](https://github.com/cyfine))
## License
This project is licensed under the MIT License - see the LICENSE file for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "dida365",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "automation, dida365, task-management, ticktick, unofficial-api",
"author": null,
"author_email": "Carter Yifeng Cheng <cyfine@github.com>",
"download_url": "https://files.pythonhosted.org/packages/d6/34/5b3e5861009a58a8950da14f999c21ff9d5b785cc525113ce947a100c089/dida365-0.1.2.tar.gz",
"platform": null,
"description": "# Dida365/TickTick API Client\n\nAn unofficial Python client library for the Dida365/TickTick API, supporting both the Chinese (Dida365) and international (TickTick) versions of the service. Built with modern async Python and robust error handling.\n\nThis is a package created to facilitate task management automation. It is not affiliated with or endorsed by Dida365 or TickTick.\n\n## API Documentation References\n\n- Dida365 API: https://developer.dida365.com/api#/openapi\n- TickTick API: https://developer.ticktick.com/docs#/openapi\n\n## OAuth2 Setup\n\n1. Get your OAuth2 credentials:\n - For TickTick: Visit https://developer.ticktick.com/manage\n - For Dida365: Visit https://developer.dida365.com/manage\n - Click \"New App\" to create a new application\n - After creation, you'll receive your Client ID and Client Secret\n\n2. Configure OAuth2 redirect URL:\n - In your Manage App page, click \"Edit\" of your newly created app\n - Add the redirect URL: `http://localhost:8080/callback` at \"OAuth redirect URL\"\n - Save the changes\n - Note: If you want to use a different redirect URL, make sure to update it in both:\n - The app settings on TickTick/Dida365 developer portal\n - Your .env file (see below)\n\n3. Configure your credentials:\n ```bash\n # .env file\n DIDA365_CLIENT_ID=your_client_id # From step 1\n DIDA365_CLIENT_SECRET=your_client_secret # From step 1\n DIDA365_REDIRECT_URI=http://localhost:8080/callback # From step 2\n DIDA365_SERVICE_TYPE=ticktick # or dida365\n ```\n\n## Features\n\n- \u2728 Full async support using `httpx`\n- \ud83d\udd12 OAuth2 authentication with automatic token management\n- \ud83d\udcdd Type-safe with Pydantic v2 models\n- \ud83c\udf10 Configurable endpoints (Dida365/TickTick)\n- \ud83d\udee1\ufe0f Comprehensive error handling\n- \u26a1 Automatic retry mechanism\n- \ud83d\udd04 Environment file integration\n- \ud83d\udcca State management for tasks and projects\n\n## Installation\n\n```bash\npip install dida365\n```\n\n## Quick Start\n\n```python\nimport asyncio\nfrom datetime import datetime, timezone\nfrom dida365 import Dida365Client, ServiceType, TaskCreate, ProjectCreate, TaskPriority\n\nasync def main():\n # Initialize client (credentials can also be loaded from .env file)\n client = Dida365Client(\n client_id=\"your_client_id\", # Optional if in .env\n client_secret=\"your_client_secret\", # Optional if in .env\n service_type=ServiceType.TICKTICK, # or DIDA365\n redirect_uri=\"http://localhost:8080/callback\", # Optional\n save_to_env=True # Automatically save credentials and tokens to .env\n )\n\n # First-time authentication:\n if not client.auth.token:\n # This will start a local server at the redirect_uri\n # and open your browser for authorization\n await client.authenticate()\n # Token will be automatically saved to .env if save_to_env=True\n\n # Create a project\n project = await client.create_project(\n ProjectCreate(\n name=\"My Project\",\n color=\"#FF0000\"\n )\n )\n\n # Create a task\n task = await client.create_task(\n TaskCreate(\n project_id=project.id,\n title=\"My new task\",\n content=\"Task description\",\n priority=TaskPriority.HIGH,\n start_date=datetime.now(timezone.utc),\n is_all_day=False,\n time_zone=\"UTC\"\n )\n )\n \n print(f\"Created task: {task.title}\")\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n## CRUD Operations\n\n### Projects\n\n```python\nfrom dida365 import ProjectCreate, ProjectUpdate, ViewMode, ProjectKind, Dida365Client\n\nasync def manage_projects(client: Dida365Client):\n # Create a project\n project = await client.create_project(\n ProjectCreate(\n name=\"My Project\",\n color=\"#FF0000\", # Optional: hex color code\n view_mode=ViewMode.KANBAN, # Optional: LIST, KANBAN, TIMELINE\n kind=ProjectKind.TASK # Optional: TASK, NOTE\n )\n )\n\n # Get project we just created\n project = await client.get_project(project_id=project.id)\n\n # Get project with all tasks and columns\n project_data = await client.get_project_with_data(project_id=project.id)\n print(f\"Project {project_data.project.name} has {len(project_data.tasks)} tasks\")\n for column in project_data.columns: # Only present in KANBAN view\n print(f\"Column: {column.name}\")\n\n # Update project\n updated_project = await client.update_project(\n ProjectUpdate(\n id=project.id,\n name=\"Updated Project Name\",\n color=\"#00FF00\",\n view_mode=ViewMode.LIST\n )\n )\n\n # Delete project\n await client.delete_project(project_id=project.id)\n\n # List all projects\n projects = await client.get_projects()\n for project in projects:\n print(f\"Project: {project.name} ({project.id})\")\n```\n\n### Tasks\n\n```python\nfrom datetime import datetime, timezone\nfrom dida365 import TaskCreate, TaskUpdate, TaskPriority, Dida365Client, Project\n\nasync def manage_tasks(client: Dida365Client, project: Project):\n # Create a task\n task = await client.create_task(\n TaskCreate(\n project_id=project.id, # Required: tasks must belong to a project\n title=\"Complete documentation\",\n content=\"Add CRUD examples\",\n priority=TaskPriority.HIGH, # Enum: NONE, LOW, MEDIUM, HIGH\n start_date=datetime.now(timezone.utc),\n due_date=datetime.now(timezone.utc),\n is_all_day=True,\n time_zone=\"UTC\"\n )\n )\n\n # Read a task\n task = await client.get_task(project_id=project.id, task_id=task.id)\n\n # Update a task\n updated_task = await client.update_task(\n TaskUpdate(\n id=task.id,\n project_id=task.project_id, # Both id and project_id are required\n title=\"Updated title\",\n content=\"Added more details\",\n priority=TaskPriority.MEDIUM\n )\n )\n\n # Complete a task\n await client.complete_task(project_id=task.project_id, task_id=task.id)\n \n # Delete a task\n await client.delete_task(project_id=task.project_id, task_id=task.id)\n```\n\n### State Management\n\nThe client maintains an internal state of tasks and projects:\n\n```python\n# Access cached state\ntasks = client.state[\"tasks\"]\nprojects = client.state[\"projects\"]\ntags = client.state[\"tags\"]\n\n# State is automatically updated when you:\n# 1. Create new items\n# 2. Update existing items\n# 3. Delete items\n```\n\n## Configuration\n\nThe client can be configured through:\n1. Environment variables\n2. Constructor parameters\n3. pyproject.toml settings\n\n### Environment Variables\n\nCreate a `.env` file:\n```bash\n# Required credentials\nDIDA365_CLIENT_ID=your_client_id\nDIDA365_CLIENT_SECRET=your_client_secret\n\n# Optional configurations\nDIDA365_SERVICE_TYPE=ticktick # or dida365 (default)\nDIDA365_ACCESS_TOKEN=your_token # Will be saved automatically after auth\nDIDA365_BASE_URL=custom_url # Optional: custom API endpoint\nDIDA365_LOG_LEVEL=INFO # Optional: DEBUG, INFO, WARNING, ERROR\n```\n\nYou can also use a custom `.env` file location:\n```python\nfrom dotenv import load_dotenv\n\nload_dotenv(\"/path/to/your/.env\")\nclient = Dida365Client() # Will load from the specified .env file\n```\n\n### Request Timeouts\n\nConfigure request timeouts in `pyproject.toml`:\n```toml\n[tool.dida365.request_timeout]\nconnect = 10.0 # Connection timeout\nread = 30.0 # Read timeout\nwrite = 30.0 # Write timeout\npool = 5.0 # Pool timeout\n```\n\n### Logging Configuration\n\n```toml\n[tool.dida365]\nlog_level = \"INFO\"\nlog_format = \"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"\nlog_date_format = \"%Y-%m-%d %H:%M:%S\"\nlog_file = \"\" # Set to a path to enable file logging\ndebug = false\n```\n\n## Error Handling\n\nThe library provides detailed error handling:\n\n```python\nfrom dida365.exceptions import (\n ApiError,\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n ValidationError\n)\n\ntry:\n task = await client.get_task(\"project_id\", \"task_id\")\nexcept NotFoundError:\n print(\"Task not found\")\nexcept AuthenticationError:\n print(\"Authentication failed - token may have expired\")\nexcept RateLimitError as e:\n print(f\"Rate limit exceeded. Retry after {e.retry_after} seconds\")\nexcept ValidationError as e:\n print(f\"Invalid data: {e}\")\nexcept ApiError as e:\n print(f\"API error: {e.status_code} - {e.message}\")\n```\n\n## Author\n\nCarter Yifeng Cheng ([@cyfine](https://github.com/cyfine))\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details. ",
"bugtrack_url": null,
"license": null,
"summary": "Unofficial Python client for Dida365/TickTick API - Community-maintained package for task automation",
"version": "0.1.2",
"project_urls": {
"Bug Tracker": "https://github.com/Cyfine/TickTick-Dida365-API-Client/issues",
"Changelog": "https://github.com/Cyfine/TickTick-Dida365-API-Client/blob/main/CHANGELOG.md",
"Documentation": "https://github.com/Cyfine/TickTick-Dida365-API-Client#readme",
"Homepage": "https://github.com/Cyfine/TickTick-Dida365-API-Client"
},
"split_keywords": [
"automation",
" dida365",
" task-management",
" ticktick",
" unofficial-api"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4acd3b7513a4dd7d97ddb4c040c49c979f87e09971173f3948919cb8963bebdb",
"md5": "7696b4f78717b35449e129a8eba0d90e",
"sha256": "078338a79c4c212081adc2dbd1c1028523d7501faf1d1130ffa5c1a91d3fe3e0"
},
"downloads": -1,
"filename": "dida365-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7696b4f78717b35449e129a8eba0d90e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 20866,
"upload_time": "2025-01-18T13:31:11",
"upload_time_iso_8601": "2025-01-18T13:31:11.968035Z",
"url": "https://files.pythonhosted.org/packages/4a/cd/3b7513a4dd7d97ddb4c040c49c979f87e09971173f3948919cb8963bebdb/dida365-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "d6345b3e5861009a58a8950da14f999c21ff9d5b785cc525113ce947a100c089",
"md5": "4023420e2d618dafc414fdc87e9c456c",
"sha256": "d804a500d62d2d8328ff84f073dd3c442ce39b6d82d2bc96d7cc8a26517b40f8"
},
"downloads": -1,
"filename": "dida365-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "4023420e2d618dafc414fdc87e9c456c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 18218,
"upload_time": "2025-01-18T13:31:15",
"upload_time_iso_8601": "2025-01-18T13:31:15.203452Z",
"url": "https://files.pythonhosted.org/packages/d6/34/5b3e5861009a58a8950da14f999c21ff9d5b785cc525113ce947a100c089/dida365-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-18 13:31:15",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Cyfine",
"github_project": "TickTick-Dida365-API-Client",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "dida365"
}