Coverage for src/qdrant_loader/connectors/jira/config.py: 94%
50 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-08 06:05 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-08 06:05 +0000
1"""Configuration for Jira connector."""
3import os
4from enum import Enum
5from typing import Self
7from pydantic import ConfigDict, Field, HttpUrl, field_validator, model_validator
9from qdrant_loader.config.source_config import SourceConfig
12class JiraDeploymentType(str, Enum):
13 """Jira deployment types."""
15 CLOUD = "cloud"
16 DATACENTER = "datacenter"
19class JiraProjectConfig(SourceConfig):
20 """Configuration for a Jira project."""
22 # Authentication
23 token: str | None = Field(
24 default=None, description="Jira API token or Personal Access Token"
25 )
26 email: str | None = Field(
27 default=None, description="Email associated with the API token (Cloud only)"
28 )
29 base_url: HttpUrl = Field(
30 ...,
31 description="Base URL of the Jira instance (e.g., 'https://your-domain.atlassian.net')",
32 )
34 # Project configuration
35 project_key: str = Field(
36 ..., description="Project key to process (e.g., 'PROJ')", min_length=1
37 )
39 # Deployment type
40 deployment_type: JiraDeploymentType = Field(
41 default=JiraDeploymentType.CLOUD,
42 description="Jira deployment type (cloud, datacenter, or server)",
43 )
45 # Rate limiting
46 requests_per_minute: int = Field(
47 default=60, description="Maximum number of requests per minute", ge=1, le=1000
48 )
50 # Pagination
51 page_size: int = Field(
52 default=100,
53 description="Number of items per page for paginated requests",
54 ge=1,
55 le=100,
56 )
58 # Attachment handling
59 download_attachments: bool = Field(
60 default=False, description="Whether to download and process issue attachments"
61 )
63 # Additional configuration
64 issue_types: list[str] = Field(
65 default=[],
66 description="Optional list of issue types to process (e.g., ['Bug', 'Story']). If empty, all types are processed.",
67 )
68 include_statuses: list[str] = Field(
69 default=[],
70 description="Optional list of statuses to include (e.g., ['Open', 'In Progress']). If empty, all statuses are included.",
71 )
73 model_config = ConfigDict(validate_default=True, arbitrary_types_allowed=True)
75 @field_validator("deployment_type", mode="before")
76 @classmethod
77 def auto_detect_deployment_type(
78 cls, v: str | JiraDeploymentType
79 ) -> JiraDeploymentType:
80 """Auto-detect deployment type if not specified."""
81 if isinstance(v, str):
82 return JiraDeploymentType(v.lower())
83 return v
85 @field_validator("token", mode="after")
86 @classmethod
87 def load_token_from_env(cls, v: str | None) -> str | None:
88 """Load token from environment variable if not provided."""
89 return v or os.getenv("JIRA_TOKEN")
91 @field_validator("email", mode="after")
92 @classmethod
93 def load_email_from_env(cls, v: str | None) -> str | None:
94 """Load email from environment variable if not provided."""
95 return v or os.getenv("JIRA_EMAIL")
97 @model_validator(mode="after")
98 def validate_auth_config(self) -> Self:
99 """Validate authentication configuration based on deployment type."""
100 if self.deployment_type == JiraDeploymentType.CLOUD:
101 # Cloud requires email and token
102 if not self.email:
103 raise ValueError("Email is required for Jira Cloud deployment")
104 if not self.token:
105 raise ValueError("API token is required for Jira Cloud deployment")
106 else:
107 # Data Center/Server requires Personal Access Token
108 if not self.token:
109 raise ValueError(
110 "Personal Access Token is required for Jira Data Center/Server deployment"
111 )
113 return self
115 @field_validator("issue_types", "include_statuses")
116 @classmethod
117 def validate_list_items(cls, v: list[str]) -> list[str]:
118 """Validate that list items are not empty strings."""
119 if any(not item.strip() for item in v):
120 raise ValueError("List items cannot be empty strings")
121 return [item.strip() for item in v]