Skip to main content
Glama
models.py19.5 kB
"""Pydantic models for Bitbucket API responses. These models handle transformation from raw API responses to clean, typed dictionaries for MCP tool responses. """ from __future__ import annotations from typing import Any, Optional from pydantic import BaseModel, field_validator # ==================== UTILS ==================== def truncate_timestamp(ts: Optional[str]) -> Optional[str]: """Truncate ISO timestamp to minute precision. 2025-01-15T14:30:45.123456Z -> 2025-01-15T14:30 """ if not ts: return ts return ts[:16] if len(ts) >= 16 else ts # ==================== REPOSITORIES ==================== class RepositorySummary(BaseModel): """Repository info for list responses.""" name: str full_name: str description: str = "" private: bool = True project: Optional[str] = None @field_validator("description", mode="before") @classmethod def truncate_description(cls, v: Any) -> str: return (v or "")[:100] @classmethod def from_api(cls, data: dict) -> "RepositorySummary": return cls( name=data.get("name", ""), full_name=data.get("full_name", ""), description=data.get("description"), private=data.get("is_private", True), project=(data.get("project") or {}).get("key"), ) class RepositoryDetail(BaseModel): """Repository info for get responses.""" name: str full_name: str description: str = "" private: bool = True created: Optional[str] = None updated: Optional[str] = None mainbranch: Optional[str] = None clone_urls: dict[str, str] = {} project: Optional[str] = None @field_validator("created", "updated", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict, clone_urls: dict[str, str]) -> "RepositoryDetail": return cls( name=data.get("name", ""), full_name=data.get("full_name", ""), description=data.get("description", ""), private=data.get("is_private", True), created=data.get("created_on"), updated=data.get("updated_on"), mainbranch=(data.get("mainbranch") or {}).get("name"), clone_urls=clone_urls, project=(data.get("project") or {}).get("key"), ) # ==================== PULL REQUESTS ==================== class PullRequestSummary(BaseModel): """PR info for list responses.""" id: int title: str state: str author: Optional[str] = None source_branch: Optional[str] = None destination_branch: Optional[str] = None url: str = "" @classmethod def from_api(cls, data: dict, url: str = "") -> "PullRequestSummary": return cls( id=data.get("id", 0), title=data.get("title", ""), state=data.get("state", ""), author=(data.get("author") or {}).get("display_name"), source_branch=(data.get("source") or {}).get("branch", {}).get("name"), destination_branch=(data.get("destination") or {}).get("branch", {}).get("name"), url=url, ) class PullRequestDetail(BaseModel): """PR info for get responses.""" id: int title: str description: str = "" state: str author: Optional[str] = None source_branch: Optional[str] = None destination_branch: Optional[str] = None created: Optional[str] = None updated: Optional[str] = None url: str = "" comment_count: int = 0 task_count: int = 0 @field_validator("created", "updated", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict, url: str = "") -> "PullRequestDetail": return cls( id=data.get("id", 0), title=data.get("title", ""), description=data.get("description", ""), state=data.get("state", ""), author=(data.get("author") or {}).get("display_name"), source_branch=(data.get("source") or {}).get("branch", {}).get("name"), destination_branch=(data.get("destination") or {}).get("branch", {}).get("name"), created=data.get("created_on"), updated=data.get("updated_on"), url=url, comment_count=data.get("comment_count", 0), task_count=data.get("task_count", 0), ) # ==================== COMMITS ==================== class CommitSummary(BaseModel): """Commit info for list responses.""" hash: str message: str author: str = "" date: Optional[str] = None @field_validator("hash", mode="before") @classmethod def truncate_hash(cls, v: Any) -> str: return (v or "")[:12] @field_validator("message", mode="before") @classmethod def first_line(cls, v: Any) -> str: return (v or "").split("\n")[0] @field_validator("date", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "CommitSummary": return cls( hash=data.get("hash", ""), message=data.get("message"), author=(data.get("author") or {}).get("raw", ""), date=data.get("date"), ) class CommitDetail(BaseModel): """Commit info for get responses.""" hash: str message: str = "" author_raw: Optional[str] = None author_user: Optional[str] = None date: Optional[str] = None parents: list[str] = [] @field_validator("date", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "CommitDetail": author = data.get("author") or {} return cls( hash=data.get("hash", ""), message=data.get("message", ""), author_raw=author.get("raw"), author_user=(author.get("user") or {}).get("display_name"), date=data.get("date"), parents=[(p.get("hash") or "")[:12] for p in data.get("parents", [])], ) # ==================== BRANCHES ==================== class BranchSummary(BaseModel): """Branch info for list responses.""" name: str commit: str message: str = "" date: Optional[str] = None @field_validator("commit", mode="before") @classmethod def truncate_hash(cls, v: Any) -> str: return (v or "")[:12] @field_validator("message", mode="before") @classmethod def first_line(cls, v: Any) -> str: return (v or "").split("\n")[0] @field_validator("date", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "BranchSummary": target = data.get("target") or {} return cls( name=data.get("name", ""), commit=target.get("hash", ""), message=target.get("message"), date=target.get("date"), ) # ==================== PIPELINES ==================== class PipelineSummary(BaseModel): """Pipeline info for list responses.""" uuid: str build_number: Optional[int] = None state: Optional[str] = None result: Optional[str] = None branch: Optional[str] = None created: Optional[str] = None @field_validator("created", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "PipelineSummary": state_data = data.get("state") or {} result_data = state_data.get("result") or {} return cls( uuid=data.get("uuid", ""), build_number=data.get("build_number"), state=state_data.get("name"), result=result_data.get("name") if result_data else None, branch=(data.get("target") or {}).get("ref_name"), created=data.get("created_on"), ) class PipelineDetail(BaseModel): """Pipeline info for get responses.""" uuid: str build_number: Optional[int] = None state: Optional[str] = None result: Optional[str] = None branch: Optional[str] = None created: Optional[str] = None completed: Optional[str] = None duration_s: Optional[int] = None @field_validator("created", "completed", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "PipelineDetail": state_data = data.get("state") or {} result_data = state_data.get("result") or {} return cls( uuid=data.get("uuid", ""), build_number=data.get("build_number"), state=state_data.get("name"), result=result_data.get("name") if result_data else None, branch=(data.get("target") or {}).get("ref_name"), created=data.get("created_on"), completed=data.get("completed_on"), duration_s=data.get("duration_in_seconds"), ) class PipelineStep(BaseModel): """Pipeline step info.""" uuid: str name: Optional[str] = None state: Optional[str] = None result: Optional[str] = None @classmethod def from_api(cls, data: dict) -> "PipelineStep": state_data = data.get("state") or {} result_data = state_data.get("result") or {} return cls( uuid=data.get("uuid", ""), name=data.get("name"), state=state_data.get("name"), result=result_data.get("name") if result_data else None, ) class PipelineVariableSummary(BaseModel): """Pipeline variable info.""" uuid: str = "" key: str = "" secured: bool = False value: Optional[str] = None # Only present for non-secured variables @classmethod def from_api(cls, data: dict) -> "PipelineVariableSummary": return cls( uuid=data.get("uuid", ""), key=data.get("key", ""), secured=data.get("secured", False), value=data.get("value"), # Will be None for secured variables ) # ==================== TAGS ==================== class TagSummary(BaseModel): """Tag info for list responses.""" name: str target: str message: Optional[str] = None tagger: Optional[str] = None date: Optional[str] = None @field_validator("date", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "TagSummary": target = data.get("target") or {} tagger = data.get("tagger") or {} tagger_raw = tagger.get("raw", "") return cls( name=data.get("name", ""), target=(target.get("hash") or "")[:12], message=data.get("message") or None, tagger=tagger_raw if tagger_raw else None, date=data.get("date"), ) def model_dump(self, **kwargs) -> dict: """Exclude None values to save tokens on lightweight tags.""" kwargs.setdefault("exclude_none", True) return super().model_dump(**kwargs) # ==================== PROJECTS ==================== class ProjectSummary(BaseModel): """Project info for list responses.""" key: str name: str description: str = "" private: bool = True created: Optional[str] = None @field_validator("description", mode="before") @classmethod def truncate_description(cls, v: Any) -> str: return (v or "")[:100] @field_validator("created", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "ProjectSummary": return cls( key=data.get("key", ""), name=data.get("name", ""), description=data.get("description"), private=data.get("is_private", True), created=data.get("created_on"), ) class ProjectDetail(BaseModel): """Project info for get responses.""" key: str name: str description: str = "" private: bool = True created: Optional[str] = None updated: Optional[str] = None @field_validator("created", "updated", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "ProjectDetail": return cls( key=data.get("key", ""), name=data.get("name", ""), description=data.get("description", ""), private=data.get("is_private", True), created=data.get("created_on"), updated=data.get("updated_on"), ) # ==================== WEBHOOKS ==================== class WebhookSummary(BaseModel): """Webhook info for list responses.""" uuid: str url: str description: str = "" events: list[str] = [] active: bool = True created: Optional[str] = None @field_validator("created", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "WebhookSummary": return cls( uuid=data.get("uuid", ""), url=data.get("url", ""), description=data.get("description", ""), events=data.get("events", []), active=data.get("active", True), created=data.get("created_at"), ) # ==================== ENVIRONMENTS & DEPLOYMENTS ==================== class EnvironmentSummary(BaseModel): """Environment info for list responses.""" uuid: str name: str environment_type: Optional[str] = None rank: Optional[int] = None @classmethod def from_api(cls, data: dict) -> "EnvironmentSummary": return cls( uuid=data.get("uuid", ""), name=data.get("name", ""), environment_type=(data.get("environment_type") or {}).get("name"), rank=data.get("rank"), ) class DeploymentSummary(BaseModel): """Deployment info for history responses.""" uuid: str state: Optional[str] = None commit: str = "" pipeline_uuid: Optional[str] = None started: Optional[str] = None completed: Optional[str] = None @field_validator("started", "completed", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "DeploymentSummary": state_data = data.get("state") or {} release = data.get("release") or {} pipeline = release.get("pipeline") or {} return cls( uuid=data.get("uuid", ""), state=state_data.get("name"), commit=((data.get("commit") or {}).get("hash") or "")[:12], pipeline_uuid=pipeline.get("uuid"), started=state_data.get("started_on"), completed=state_data.get("completed_on"), ) # ==================== COMMENTS ==================== class CommentSummary(BaseModel): """Comment info for list responses.""" id: int author: Optional[str] = None content: str = "" created: Optional[str] = None updated: Optional[str] = None inline: Optional[dict] = None @field_validator("created", "updated", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "CommentSummary": return cls( id=data.get("id", 0), author=(data.get("user") or {}).get("display_name"), content=(data.get("content") or {}).get("raw", ""), created=data.get("created_on"), updated=data.get("updated_on"), inline=data.get("inline"), ) # ==================== COMMIT STATUSES ==================== class CommitStatus(BaseModel): """Commit status info.""" key: str name: Optional[str] = None state: str description: str = "" url: Optional[str] = None created: Optional[str] = None updated: Optional[str] = None @field_validator("created", "updated", mode="before") @classmethod def truncate_ts(cls, v: Any) -> Optional[str]: return truncate_timestamp(v) @classmethod def from_api(cls, data: dict) -> "CommitStatus": return cls( key=data.get("key", ""), name=data.get("name"), state=data.get("state", ""), description=data.get("description", ""), url=data.get("url"), created=data.get("created_on"), updated=data.get("updated_on"), ) # ==================== BRANCH RESTRICTIONS ==================== class BranchRestriction(BaseModel): """Branch restriction info.""" id: int kind: str pattern: str = "" branch_match_kind: Optional[str] = None branch_type: Optional[str] = None value: Optional[int] = None users: list[str] = [] groups: list[str] = [] @classmethod def from_api(cls, data: dict) -> "BranchRestriction": return cls( id=data.get("id", 0), kind=data.get("kind", ""), pattern=data.get("pattern", ""), branch_match_kind=data.get("branch_match_kind"), branch_type=data.get("branch_type"), value=data.get("value"), users=[u.get("display_name", "") for u in data.get("users", [])], groups=[g.get("name", "") for g in data.get("groups", [])], ) # ==================== PERMISSIONS ==================== class UserPermission(BaseModel): """User permission info.""" user: Optional[str] = None account_id: Optional[str] = None permission: str @classmethod def from_api(cls, data: dict) -> "UserPermission": user_data = data.get("user") or {} return cls( user=user_data.get("display_name"), account_id=user_data.get("account_id"), permission=data.get("permission", ""), ) class GroupPermission(BaseModel): """Group permission info.""" group: Optional[str] = None slug: Optional[str] = None permission: str @classmethod def from_api(cls, data: dict) -> "GroupPermission": group_data = data.get("group") or {} return cls( group=group_data.get("name"), slug=group_data.get("slug"), permission=data.get("permission", ""), ) # ==================== FILE/DIRECTORY ==================== class DirectoryEntry(BaseModel): """Directory entry info.""" path: str type: str # "commit_file" or "commit_directory" size: Optional[int] = None @classmethod def from_api(cls, data: dict) -> "DirectoryEntry": return cls( path=data.get("path", ""), type=data.get("type", ""), size=data.get("size"), )

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JaviMaligno/mcp-server-bitbucket'

If you have feedback or need assistance with the MCP directory API, please join our Discord server