CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-linear-api

A comprehensive Python wrapper for the Linear API with rich Pydantic models, simplified workflows, and an object-oriented design.

Overview
Eval results
Files

user-management.mddocs/

User Management

User operations including profile access, team memberships, issue assignments, and organizational data management.

Capabilities

Core User Operations

Basic user management operations for accessing user profiles and information.

def get(self, user_id: str) -> LinearUser:
    """
    Fetch a Linear user by ID with comprehensive user details and organization information.

    Args:
        user_id: The ID of the user to fetch

    Returns:
        LinearUser with complete details

    Raises:
        ValueError: If user not found
    """

def get_all(self) -> Dict[str, LinearUser]:
    """
    Get all users in the organization.

    Returns:
        Dictionary mapping user IDs to LinearUser objects
    """

def get_me(self) -> LinearUser:
    """
    Get the current user based on the API key.

    Returns:
        LinearUser object for the authenticated user

    Raises:
        ValueError: If authentication fails
    """

Usage examples:

from linear_api import LinearClient

client = LinearClient()

# Get current user
me = client.users.get_me()
print(f"Current user: {me.name} ({me.email})")
print(f"Admin: {me.admin}, Active: {me.active}")

# Get specific user
user = client.users.get("user-id")
print(f"User: {user.displayName} - {user.email}")

# Get all organization users
all_users = client.users.get_all()
print(f"Organization has {len(all_users)} users")

User Discovery

Find users by various identifiers and attributes.

def get_email_map(self) -> Dict[str, str]:
    """
    Get a mapping of user IDs to their email addresses.

    Returns:
        Dictionary mapping user IDs to email addresses
    """

def get_id_by_email(self, email: str) -> str:
    """
    Get a user ID by their email address.

    Args:
        email: The email address to search for

    Returns:
        User ID string

    Raises:
        ValueError: If user not found
    """

def get_id_by_name(self, name: str) -> str:
    """
    Get a user ID by their name (fuzzy match with exact, case-insensitive,
    and partial matching).

    Args:
        name: The name to search for

    Returns:
        User ID string

    Raises:
        ValueError: If user not found
    """

Usage examples:

# Get email mapping for all users
email_map = client.users.get_email_map()
print(f"Found {len(email_map)} users with emails")

# Find user by email
user_id = client.users.get_id_by_email("john.doe@company.com")
user = client.users.get(user_id)

# Find user by name (fuzzy matching)
user_id = client.users.get_id_by_name("John Doe")
# Also works with partial matches:
# user_id = client.users.get_id_by_name("john")
# user_id = client.users.get_id_by_name("John")

Issue Management

Access issues assigned to or created by users.

def get_assigned_issues(self, user_id: str) -> Dict[str, LinearIssue]:
    """
    Get issues assigned to a user.

    Args:
        user_id: The ID of the user

    Returns:
        Dictionary mapping issue IDs to LinearIssue objects
    """

def get_created_issues(self, user_id: str) -> List[Dict[str, Any]]:
    """
    Get issues created by a user with basic issue information.

    Args:
        user_id: The ID of the user

    Returns:
        List of issue dictionaries
    """

Usage examples:

# Get user's assigned issues
assigned_issues = client.users.get_assigned_issues("user-id")
print(f"User has {len(assigned_issues)} assigned issues")

# Group by status
from collections import defaultdict
by_status = defaultdict(list)
for issue in assigned_issues.values():
    by_status[issue.state.name].append(issue)

print("Issues by status:")
for status, issues in by_status.items():
    print(f"  {status}: {len(issues)} issues")

# Get issues created by user
created_issues = client.users.get_created_issues("user-id")
print(f"User has created {len(created_issues)} issues")

Team Membership

Access user team memberships and organizational relationships.

def get_team_memberships(self, user_id: str) -> List[Dict[str, Any]]:
    """
    Get team memberships for a user with team information included.

    Args:
        user_id: The ID of the user

    Returns:
        List of team membership dictionaries
    """

def get_teams(self, user_id: str) -> List[LinearTeam]:
    """
    Get teams that a user is a member of.

    Args:
        user_id: The ID of the user

    Returns:
        List of LinearTeam objects
    """

Usage examples:

# Get user's team memberships
memberships = client.users.get_team_memberships("user-id")
print(f"User is a member of {len(memberships)} teams")

for membership in memberships:
    team_info = membership.get('team', {})
    is_owner = membership.get('owner', False)
    print(f"  - {team_info.get('name')} {'(Owner)' if is_owner else ''}")

# Get teams directly
teams = client.users.get_teams("user-id")
for team in teams:
    print(f"Team: {team.name} ({team.key})")

Draft Management

Access document and issue drafts created by users.

def get_drafts(self, user_id: str) -> List[Draft]:
    """
    Get document drafts created by a user.

    Args:
        user_id: The ID of the user

    Returns:
        List of Draft objects
    """

def get_issue_drafts(self, user_id: str) -> List[IssueDraft]:
    """
    Get issue drafts created by a user.

    Args:
        user_id: The ID of the user

    Returns:
        List of IssueDraft objects
    """

Usage examples:

# Get user's document drafts
drafts = client.users.get_drafts("user-id")
print(f"User has {len(drafts)} document drafts")

# Get user's issue drafts
issue_drafts = client.users.get_issue_drafts("user-id")
print(f"User has {len(issue_drafts)} issue drafts")

Cache Management

Control caching for user-related data.

def invalidate_cache(self) -> None:
    """
    Invalidate all user-related caches.
    """

Usage examples:

# Clear user caches
client.users.invalidate_cache()

User Properties and Attributes

The LinearUser model includes comprehensive user information:

Core User Fields

user = client.users.get_me()

# Required fields
print(f"ID: {user.id}")
print(f"Name: {user.name}")
print(f"Display Name: {user.displayName}")
print(f"Email: {user.email}")
print(f"Created: {user.createdAt}")
print(f"Updated: {user.updatedAt}")

# Boolean status fields
print(f"Active: {user.active}")
print(f"Admin: {user.admin}")
print(f"App User: {user.app}")
print(f"Guest: {user.guest}")
print(f"Is Me: {user.isMe}")

Optional User Fields

# Optional profile information
if user.avatarUrl:
    print(f"Avatar: {user.avatarUrl}")

if user.description:
    print(f"Description: {user.description}")

if user.timezone:
    print(f"Timezone: {user.timezone}")

if user.statusEmoji:
    print(f"Status: {user.statusEmoji} {user.statusLabel}")

# Usage statistics
if hasattr(user, 'createdIssueCount'):
    print(f"Created issues: {user.createdIssueCount}")

# Calendar integration
if user.calendarHash:
    print(f"Calendar hash: {user.calendarHash}")

Advanced User Workflows

User Activity Analysis

def analyze_user_activity(user_id: str):
    user = client.users.get(user_id)
    assigned_issues = client.users.get_assigned_issues(user_id)
    created_issues = client.users.get_created_issues(user_id)
    teams = client.users.get_teams(user_id)

    print(f"User Activity Report: {user.displayName}")
    print(f"Email: {user.email}")
    print(f"Status: {'Active' if user.active else 'Inactive'}")
    print(f"Admin: {'Yes' if user.admin else 'No'}")
    print(f"Teams: {len(teams)}")
    print(f"Assigned Issues: {len(assigned_issues)}")
    print(f"Created Issues: {len(created_issues)}")

    # Analyze assigned issue states
    if assigned_issues:
        state_counts = {}
        for issue in assigned_issues.values():
            state = issue.state.name
            state_counts[state] = state_counts.get(state, 0) + 1

        print("\nAssigned Issues by State:")
        for state, count in state_counts.items():
            print(f"  {state}: {count}")

    # List teams
    if teams:
        print("\nTeam Memberships:")
        for team in teams:
            print(f"  - {team.name} ({team.key})")

    return {
        "user": user,
        "team_count": len(teams),
        "assigned_issues": len(assigned_issues),
        "created_issues": len(created_issues)
    }

# Analyze user activity
activity = analyze_user_activity("user-id")

Team Member Directory

def create_team_directory():
    all_users = client.users.get_all()

    # Filter active users
    active_users = {uid: user for uid, user in all_users.items() if user.active}

    # Group by team membership
    team_members = {}

    for user_id, user in active_users.items():
        teams = client.users.get_teams(user_id)
        for team in teams:
            if team.id not in team_members:
                team_members[team.id] = {
                    'team': team,
                    'members': []
                }
            team_members[team.id]['members'].append(user)

    print("Team Directory:")
    print("=" * 50)

    for team_id, team_data in team_members.items():
        team = team_data['team']
        members = team_data['members']

        print(f"\n{team.name} ({team.key})")
        print("-" * 30)

        # Sort members by name
        members.sort(key=lambda u: u.displayName.lower())

        for member in members:
            admin_badge = " [ADMIN]" if member.admin else ""
            print(f"  • {member.displayName} ({member.email}){admin_badge}")

        print(f"    Total: {len(members)} members")

# Create directory
create_team_directory()

User Workload Analysis

def analyze_workloads():
    all_users = client.users.get_all()
    workloads = []

    for user_id, user in all_users.items():
        if not user.active:
            continue

        assigned_issues = client.users.get_assigned_issues(user_id)

        # Calculate workload metrics
        total_issues = len(assigned_issues)
        in_progress = sum(1 for issue in assigned_issues.values()
                         if issue.state.type == 'started')

        workloads.append({
            'user': user,
            'total_issues': total_issues,
            'in_progress': in_progress,
            'workload_score': in_progress + (total_issues * 0.3)
        })

    # Sort by workload
    workloads.sort(key=lambda w: w['workload_score'], reverse=True)

    print("User Workload Analysis:")
    print("=" * 60)
    print(f"{'Name':<25} {'Total':<8} {'Active':<8} {'Workload':<10}")
    print("-" * 60)

    for workload in workloads[:20]:  # Top 20
        user = workload['user']
        print(f"{user.displayName:<25} {workload['total_issues']:<8} "
              f"{workload['in_progress']:<8} {workload['workload_score']:<10.1f}")

# Analyze workloads
analyze_workloads()

User Search and Filtering

def search_users(query: str, filters: dict = None):
    """
    Search for users with flexible criteria.

    Args:
        query: Search term for name or email
        filters: Additional filters (active, admin, teams, etc.)
    """
    all_users = client.users.get_all()
    results = []

    query_lower = query.lower()

    for user_id, user in all_users.items():
        # Text search
        matches_query = (
            query_lower in user.name.lower() or
            query_lower in user.displayName.lower() or
            query_lower in user.email.lower()
        )

        if not matches_query:
            continue

        # Apply filters
        if filters:
            if 'active' in filters and user.active != filters['active']:
                continue
            if 'admin' in filters and user.admin != filters['admin']:
                continue

        results.append(user)

    # Sort by relevance (exact matches first, then by name)
    def sort_key(user):
        exact_name = user.name.lower() == query_lower
        exact_display = user.displayName.lower() == query_lower
        exact_email = user.email.lower() == query_lower

        return (not (exact_name or exact_display or exact_email), user.displayName.lower())

    results.sort(key=sort_key)

    print(f"Search results for '{query}':")
    for user in results[:10]:  # Top 10 results
        status = "Active" if user.active else "Inactive"
        admin = " [ADMIN]" if user.admin else ""
        print(f"  • {user.displayName} ({user.email}) - {status}{admin}")

    return results

# Search examples
search_users("john")  # Find users named John
search_users("@company.com")  # Find users by email domain
search_users("", {'active': True, 'admin': True})  # Find active admins

Organization Metrics

def get_organization_metrics():
    all_users = client.users.get_all()

    # Calculate metrics
    total_users = len(all_users)
    active_users = sum(1 for user in all_users.values() if user.active)
    admin_users = sum(1 for user in all_users.values() if user.admin)
    guest_users = sum(1 for user in all_users.values() if user.guest)

    print("Organization User Metrics:")
    print("=" * 40)
    print(f"Total Users: {total_users}")
    print(f"Active Users: {active_users} ({active_users/total_users:.1%})")
    print(f"Admin Users: {admin_users} ({admin_users/total_users:.1%})")
    print(f"Guest Users: {guest_users} ({guest_users/total_users:.1%})")
    print(f"Inactive Users: {total_users - active_users}")

    # Get all teams for team membership analysis
    all_teams = client.teams.get_all()
    team_memberships = 0

    for user in all_users.values():
        if user.active:
            teams = client.users.get_teams(user.id)
            team_memberships += len(teams)

    avg_teams = team_memberships / active_users if active_users > 0 else 0
    print(f"Average teams per active user: {avg_teams:.1f}")

    return {
        'total': total_users,
        'active': active_users,
        'admin': admin_users,
        'guest': guest_users,
        'avg_teams_per_user': avg_teams
    }

# Get organization metrics
metrics = get_organization_metrics()

Install with Tessl CLI

npx tessl i tessl/pypi-linear-api

docs

cache-utilities.md

client-management.md

data-models-types.md

index.md

issue-operations.md

project-management.md

team-administration.md

user-management.md

tile.json