Skip to main content

Robust Python client for Zymmr API built on Frappe Framework v14

Project description

Zymmr Client

PyPI version Python 3.10+ MIT License

A robust Python client library for interacting with Zymmr Project Management API, built on Frappe Framework v14.

✨ Features

  • 🚀 Simple and Intuitive: Clean API interface inspired by frappe-client but more robust
  • 🔐 Frappe Authentication: Session-based authentication with username/password
  • 📋 Complete DocType Access: Get any DocType from your Zymmr instance
  • 🏗️ Resource-Based API: Modern hierarchical API with Projects resource
  • 🔄 Robust Error Handling: Custom exceptions for all Frappe API scenarios
  • Retry Logic: Exponential backoff for network failures
  • 💡 Type Safety: Full type hints for excellent developer experience
  • 🎯 Production Ready: Built for real-world Frappe applications

📦 Installation

Using uv (Recommended)

uv add zymmr-client

Using pip

pip install zymmr-client

Using poetry

poetry add zymmr-client

🚀 Quick Start

Generic API (Works with any DocType)

from zymmr_client import ZymmrClient

# Initialize the client with your Zymmr instance
client = ZymmrClient(
    base_url="https://zymmr.yourdomain.com",
    username="your-username",
    password="your-password"
)

# Get list of projects
projects = client.get_list("Project",
                          fields=["name", "title", "status"],
                          limit_page_length=10)

# Get list of work items with filters
work_items = client.get_list("Work Item",
                           fields=["key", "title", "status", "priority"],
                           filters={"status": ["in", ["Open", "In Progress"]]},
                           order_by="priority desc")

# Get a specific document
project = client.get_doc("Project", "PROJ-001")

# Create a new work item
new_work_item = client.insert("Work Item", {
    "title": "Fix authentication bug",
    "project": "PROJ-001",
    "type": "Bug",
    "priority": "High"
})

# Update work item status
client.update("Work Item", new_work_item["name"], {
    "status": "In Progress",
    "assignee": "developer@company.com"
})

# Test connection
if client.ping():
    print("✅ Connected to Zymmr!")

# Get current user info
user_info = client.get_user_info()
print(f"Logged in as: {user_info['username']}")

Resource-Based API (New) - Projects

from zymmr_client import ZymmrClient

# Initialize the client
client = ZymmrClient(
    base_url="https://zymmr.yourdomain.com",
    username="your-username",
    password="your-password"
)

# List all projects
projects = client.projects.list(
    fields=["title", "key", "status"],
    filters={"status": "Active"},
    order_by="title"
)

# Get active projects only
active_projects = client.projects.get_active()

# Get projects by user
my_projects = client.projects.get_by_user("pm@company.com")

# Get specific project
project = client.projects.get("PROJ-001")

# Create new project
new_project = client.projects.create({
    "title": "Website Redesign",
    "key": "WEB-REDESIGN",
    "description": "Complete website overhaul",
    "lead": "pm@company.com",
    "start_date": "2024-01-15"
})

# Update project
client.projects.update("PROJ-001", {
    "status": "In Progress",
    "end_date": "2024-06-30"
})

# Delete project
client.projects.delete("OLD-PROJ")

# Get project with analytics
analytics = client.projects.get_analytics("PROJ-001", period="last_month")
print(f"Project analytics: {analytics}")

🔧 Configuration

Basic Configuration

from zymmr_client import ZymmrClient

# Basic initialization
client = ZymmrClient(
    base_url="https://zymmr.yourdomain.com",
    username="your-username",
    password="your-password"
)

# With custom timeout and retry settings
client = ZymmrClient(
    base_url="https://zymmr.yourdomain.com", 
    username="your-username",
    password="your-password",
    timeout=60,          # Request timeout in seconds
    max_retries=5,       # Max retry attempts
    debug=True           # Enable debug logging
)

Context Manager (Recommended)

# Automatic session cleanup
with ZymmrClient(base_url, username, password) as client:
    projects = client.get_list("Project")
    # Session automatically closed when done

📖 API Reference

Generic API (Works with any DocType)

get_list(doctype, **kwargs)

Get a list of documents from any Frappe DocType:

# Basic usage
projects = client.get_list("Project")

# With specific fields
projects = client.get_list("Project",
                          fields=["name", "title", "status"])

# With filters
open_work_items = client.get_list("Work Item",
                                 fields=["key", "title", "priority"],
                                 filters={"status": "Open"},
                                 order_by="priority desc",
                                 limit_page_length=50)

# Complex filters
recent_projects = client.get_list("Project",
                                 filters={
                                     "status": ["in", ["Active", "On Hold"]],
                                     "creation": [">", "2024-01-01"]
                                 })

get_doc(doctype, name, fields=None)

Get a specific document:

# Get full document
project = client.get_doc("Project", "PROJ-001")

# Get specific fields only
work_item = client.get_doc("Work Item", "WI-123",
                          fields=["key", "title", "status"])

insert(doctype, data)

Create a new document:

# Create a new work item
work_item = client.insert("Work Item", {
    "title": "Implement user dashboard",
    "project": "PROJ-001",
    "type": "Story",
    "priority": "Medium",
    "description": "Create a user dashboard with key metrics"
})

# Create a new project
project = client.insert("Project", {
    "title": "Mobile App Development",
    "key": "MAD",
    "description": "iOS and Android mobile application",
    "lead": "pm@company.com"
})

# Create a time log
time_log = client.insert("Time Log", {
    "work_item": "WI-123",
    "time": "2h 30m",
    "description": "Implemented authentication logic",
    "is_billable": True
})

update(doctype, name, data)

Update an existing document:

# Update work item status and assignee
client.update("Work Item", "WI-123", {
    "status": "In Progress",
    "assignee": "developer@company.com",
    "start_date": "2024-01-15"
})

# Update project end date
client.update("Project", "PROJ-001", {
    "end_date": "2024-12-31",
    "status": "Active"
})

# Update time log
client.update("Time Log", "TL-456", {
    "time": "3h",
    "description": "Updated: Fixed authentication and added tests"
})

delete(doctype, name)

Delete a document:

# Delete a work item
success = client.delete("Work Item", "WI-123")
if success:
    print("Work item deleted successfully")

# Delete a time log
client.delete("Time Log", "TL-456")

# Delete a project (be careful!)
client.delete("Project", "OLD-PROJ")

Utility Methods

# Test connection
is_connected = client.ping()

# Get current user information
user_info = client.get_user_info()

# Check authentication status
if client.is_authenticated:
    print("Client is authenticated")

# Clean session cleanup
client.close()

Resource-Based API (New) - Projects

client.projects.list(**kwargs)

Get a list of projects:

# List all projects
projects = client.projects.list()

# With specific fields
projects = client.projects.list(
    fields=["title", "key", "status", "lead"]
)

# With filters
active_projects = client.projects.list(
    filters={"status": "Active"},
    order_by="title"
)

# With pagination
projects = client.projects.list(
    limit_start=0,
    limit_page_length=20
)

client.projects.get(name, **kwargs)

Get a specific project:

# Get project by ID
project = client.projects.get("PROJ-001")

# Get project with specific fields only
project = client.projects.get("PROJ-001",
                           fields=["title", "status", "lead"])

client.projects.create(data)

Create a new project:

# Basic project creation
project = client.projects.create({
    "title": "Website Redesign",
    "key": "WEB-REDESIGN",
    "description": "Complete website overhaul",
    "lead": "pm@company.com"
})

# Project with all fields
project = client.projects.create({
    "title": "Mobile App Development",
    "key": "MOBILE-APP",
    "description": "iOS and Android mobile application",
    "lead": "pm@company.com",
    "start_date": "2024-01-15",
    "end_date": "2024-06-30",
    "status": "Planning"
})

client.projects.update(name, data)

Update an existing project:

# Update project status
client.projects.update("PROJ-001", {
    "status": "In Progress"
})

# Update multiple fields
client.projects.update("PROJ-001", {
    "status": "Completed",
    "end_date": "2024-01-30",
    "description": "Project completed successfully"
})

client.projects.delete(name)

Delete a project:

# Delete a project
success = client.projects.delete("OLD-PROJ")
if success:
    print("Project deleted successfully")

Convenience Methods

# Get active projects only
active_projects = client.projects.get_active()

# Get projects by user
user_projects = client.projects.get_by_user("pm@company.com")

# Get project analytics
analytics = client.projects.get_analytics("PROJ-001", period="last_month")

Available DocTypes

Access any DocType from your Zymmr/Frappe instance:

# Standard Frappe DocTypes
users = client.get_list("User")
doctypes = client.get_list("DocType")

# Zymmr-specific DocTypes
projects = client.get_list("Project")
work_items = client.get_list("Work Item")  # Main work tracking entity
time_logs = client.get_list("Time Log")
sprints = client.get_list("Sprint")

# Custom DocTypes (based on your Zymmr setup)
custom_docs = client.get_list("Your Custom DocType")

🎯 Common Workflows

Complete Work Item Lifecycle

# 1. Create a new work item
work_item = client.insert("Work Item", {
    "title": "Add user notifications",
    "project": "PROJ-001",
    "type": "Feature",
    "priority": "Medium",
    "description": "Implement email and in-app notifications"
})

print(f"Created work item: {work_item['key']}")

# 2. Start working on it
client.update("Work Item", work_item["name"], {
    "status": "In Progress",
    "assignee": "developer@company.com",
    "actual_start_date": "2024-01-15"
})

# 3. Log time while working
client.insert("Time Log", {
    "work_item": work_item["key"],
    "time": "2h",
    "description": "Implemented notification service",
    "is_billable": True
})

# 4. Complete the work item
client.update("Work Item", work_item["name"], {
    "status": "Done",
    "completion_date": "2024-01-16T14:30:00"
})

Project Management Operations

# Create a new project
project = client.insert("Project", {
    "title": "Customer Portal",
    "key": "CP",
    "description": "Self-service customer portal",
    "lead": "pm@company.com",
    "start_date": "2024-01-01",
    "end_date": "2024-06-30"
})

# Add work items to the project
work_items = [
    {"title": "User authentication", "type": "Story", "priority": "High"},
    {"title": "Dashboard design", "type": "Story", "priority": "Medium"},
    {"title": "Payment integration", "type": "Feature", "priority": "High"}
]

for item_data in work_items:
    item_data["project"] = project["key"]
    client.insert("Work Item", item_data)

# Get project progress
project_work_items = client.get_list("Work Item",
                                   filters={"project": project["key"]},
                                   fields=["key", "title", "status"])

print(f"Project {project['key']} has {len(project_work_items)} work items")

Project Resource Operations

# Create a new project using resource API
project = client.projects.create({
    "title": "Customer Portal",
    "key": "CP",
    "description": "Self-service customer portal",
    "lead": "pm@company.com",
    "start_date": "2024-01-01",
    "end_date": "2024-06-30",
    "status": "Planning"
})

print(f"Created project: {project['title']} ({project['key']})")

# Update project status
client.projects.update(project["name"], {
    "status": "In Progress",
    "actual_start_date": "2024-01-15"
})

# Get project details
updated_project = client.projects.get(project["name"])
print(f"Project status: {updated_project['status']}")

# List projects with convenience methods
active_projects = client.projects.get_active()
print(f"Found {len(active_projects)} active projects")

# Get projects by user
my_projects = client.projects.get_by_user("pm@company.com")
print(f"Found {len(my_projects)} projects I'm leading")

# Get project analytics
analytics = client.projects.get_analytics(project["name"], period="last_month")
print(f"Project analytics: {analytics}")

Project Listing and Filtering

Generic API Approach

# Get all projects
all_projects = client.get_list("Project", limit_page_length=50)

# Get active projects only
active_projects = client.get_list("Project",
                                 filters={"status": "Active"},
                                 order_by="title")

# Get projects by lead
my_projects = client.get_list("Project",
                             filters={"lead": "pm@company.com"},
                             fields=["title", "key", "status", "start_date"])

# Get recently created projects
recent_projects = client.get_list("Project",
                                 filters={"creation": [">", "2024-01-01"]},
                                 order_by="creation desc",
                                 limit_page_length=10)

Resource-Based API Approach

# Get all projects
all_projects = client.projects.list(limit_page_length=50)

# Get active projects (convenience method)
active_projects = client.projects.get_active()

# Get projects by user (convenience method)
my_projects = client.projects.get_by_user("pm@company.com")

# Get projects with filters
filtered_projects = client.projects.list(
    filters={"status": ["Active", "Planning"]},
    order_by="title"
)

# Get projects with specific fields only
project_summaries = client.projects.list(
    fields=["title", "key", "status", "lead"],
    limit_page_length=20
)

Project CRUD Operations

Generic API

# Create
project = client.insert("Project", {
    "title": "New Website",
    "key": "WEBSITE",
    "lead": "pm@company.com"
})

# Read
project_data = client.get_doc("Project", "WEBSITE")

# Update
client.update("Project", "WEBSITE", {
    "status": "In Progress",
    "start_date": "2024-01-20"
})

# Delete
client.delete("Project", "WEBSITE")

Resource-Based API

# Create
project = client.projects.create({
    "title": "New Website",
    "key": "WEBSITE",
    "lead": "pm@company.com"
})

# Read
project_data = client.projects.get("WEBSITE")

# Update
client.projects.update("WEBSITE", {
    "status": "In Progress",
    "start_date": "2024-01-20"
})

# Delete
client.projects.delete("WEBSITE")

Time Tracking & Reporting

from datetime import datetime, timedelta

# Get time logs for the last week
last_week = datetime.now() - timedelta(days=7)
recent_logs = client.get_list("Time Log",
                             filters={"date": [">", last_week.isoformat()]},
                             fields=["work_item", "time", "author", "is_billable"])

# Calculate total billable hours
total_billable = sum(
    float(log["time"].replace("h", "").replace("m", ""))
    for log in recent_logs
    if log["is_billable"]
)

print(f"Total billable hours this week: {total_billable}")

API Usage Patterns

You can use both APIs together as needed:

# Generic API for any DocType
work_items = client.get_list("Work Item", filters={"status": "Open"})

# Resource API for Projects specifically
projects = client.projects.get_active()

# Mixed usage is perfectly fine
active_projects = client.projects.get_active()
users = client.get_list("User")
time_logs = client.get_list("Time Log")

🛠️ Development

This project uses uv for dependency management and packaging:

# Clone the repository
git clone https://github.com/kiran-harbak/zymmr-client.git
cd zymmr-client

# Install dependencies
uv sync

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=zymmr_client

# Lint code
uv run ruff check .

# Format code
uv run ruff format .

# Type check
uv run mypy src/

# Build package
uv build

🧪 Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=zymmr_client --cov-report=html

# Run specific test file
uv run pytest tests/test_client.py

# Run with verbose output
uv run pytest -v

🤝 Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📝 Changelog

See CHANGELOG.md for a list of changes and version history.

🐛 Issues

If you encounter any issues or have feature requests, please create an issue on GitHub.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

👤 Author

Kiran Harbak

🙏 Acknowledgments

  • Inspired by frappe-client but rebuilt for modern Python
  • Powered by uv for fast dependency management

If you find this package useful, please consider giving it a star on GitHub!

Package available on PyPI: https://pypi.org/project/zymmr-client/

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zymmr_client-0.2.1.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zymmr_client-0.2.1-py3-none-any.whl (22.3 kB view details)

Uploaded Python 3

File details

Details for the file zymmr_client-0.2.1.tar.gz.

File metadata

  • Download URL: zymmr_client-0.2.1.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.3

File hashes

Hashes for zymmr_client-0.2.1.tar.gz
Algorithm Hash digest
SHA256 727c0d53b762475c54efdb4ffd6f1c3eea0aa03b76a282cd5ecdea8c6175951d
MD5 3fadae7fa0b85cbc5341a693115a3793
BLAKE2b-256 91b7fc67b4475eba71ffc7b75cfed319d6529de833d822d602873b1cf1965e23

See more details on using hashes here.

File details

Details for the file zymmr_client-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for zymmr_client-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 10f6a38ba2b311c031c32a27b99e9626f71f48115d5086ea3daa875c0ec2d10e
MD5 6adc632b0aadb68067ef4f38ec4b4ead
BLAKE2b-256 4a1f62f02eb0a3d49cd4e7690520b28e1b8478b4fb40b47c7ff5f0d6e84867cc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page