airbyte-agent-jira 0.1.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. airbyte_agent_jira/__init__.py +91 -0
  2. airbyte_agent_jira/_vendored/__init__.py +1 -0
  3. airbyte_agent_jira/_vendored/connector_sdk/__init__.py +82 -0
  4. airbyte_agent_jira/_vendored/connector_sdk/auth_strategies.py +1123 -0
  5. airbyte_agent_jira/_vendored/connector_sdk/auth_template.py +135 -0
  6. airbyte_agent_jira/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
  7. airbyte_agent_jira/_vendored/connector_sdk/cloud_utils/client.py +213 -0
  8. airbyte_agent_jira/_vendored/connector_sdk/connector_model_loader.py +965 -0
  9. airbyte_agent_jira/_vendored/connector_sdk/constants.py +78 -0
  10. airbyte_agent_jira/_vendored/connector_sdk/exceptions.py +23 -0
  11. airbyte_agent_jira/_vendored/connector_sdk/executor/__init__.py +31 -0
  12. airbyte_agent_jira/_vendored/connector_sdk/executor/hosted_executor.py +197 -0
  13. airbyte_agent_jira/_vendored/connector_sdk/executor/local_executor.py +1574 -0
  14. airbyte_agent_jira/_vendored/connector_sdk/executor/models.py +190 -0
  15. airbyte_agent_jira/_vendored/connector_sdk/extensions.py +694 -0
  16. airbyte_agent_jira/_vendored/connector_sdk/http/__init__.py +37 -0
  17. airbyte_agent_jira/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
  18. airbyte_agent_jira/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
  19. airbyte_agent_jira/_vendored/connector_sdk/http/config.py +98 -0
  20. airbyte_agent_jira/_vendored/connector_sdk/http/exceptions.py +119 -0
  21. airbyte_agent_jira/_vendored/connector_sdk/http/protocols.py +114 -0
  22. airbyte_agent_jira/_vendored/connector_sdk/http/response.py +102 -0
  23. airbyte_agent_jira/_vendored/connector_sdk/http_client.py +686 -0
  24. airbyte_agent_jira/_vendored/connector_sdk/introspection.py +262 -0
  25. airbyte_agent_jira/_vendored/connector_sdk/logging/__init__.py +11 -0
  26. airbyte_agent_jira/_vendored/connector_sdk/logging/logger.py +264 -0
  27. airbyte_agent_jira/_vendored/connector_sdk/logging/types.py +92 -0
  28. airbyte_agent_jira/_vendored/connector_sdk/observability/__init__.py +11 -0
  29. airbyte_agent_jira/_vendored/connector_sdk/observability/models.py +19 -0
  30. airbyte_agent_jira/_vendored/connector_sdk/observability/redactor.py +81 -0
  31. airbyte_agent_jira/_vendored/connector_sdk/observability/session.py +94 -0
  32. airbyte_agent_jira/_vendored/connector_sdk/performance/__init__.py +6 -0
  33. airbyte_agent_jira/_vendored/connector_sdk/performance/instrumentation.py +57 -0
  34. airbyte_agent_jira/_vendored/connector_sdk/performance/metrics.py +93 -0
  35. airbyte_agent_jira/_vendored/connector_sdk/schema/__init__.py +75 -0
  36. airbyte_agent_jira/_vendored/connector_sdk/schema/base.py +161 -0
  37. airbyte_agent_jira/_vendored/connector_sdk/schema/components.py +239 -0
  38. airbyte_agent_jira/_vendored/connector_sdk/schema/connector.py +131 -0
  39. airbyte_agent_jira/_vendored/connector_sdk/schema/extensions.py +109 -0
  40. airbyte_agent_jira/_vendored/connector_sdk/schema/operations.py +146 -0
  41. airbyte_agent_jira/_vendored/connector_sdk/schema/security.py +223 -0
  42. airbyte_agent_jira/_vendored/connector_sdk/secrets.py +182 -0
  43. airbyte_agent_jira/_vendored/connector_sdk/telemetry/__init__.py +10 -0
  44. airbyte_agent_jira/_vendored/connector_sdk/telemetry/config.py +32 -0
  45. airbyte_agent_jira/_vendored/connector_sdk/telemetry/events.py +58 -0
  46. airbyte_agent_jira/_vendored/connector_sdk/telemetry/tracker.py +151 -0
  47. airbyte_agent_jira/_vendored/connector_sdk/types.py +245 -0
  48. airbyte_agent_jira/_vendored/connector_sdk/utils.py +60 -0
  49. airbyte_agent_jira/_vendored/connector_sdk/validation.py +822 -0
  50. airbyte_agent_jira/connector.py +978 -0
  51. airbyte_agent_jira/connector_model.py +2827 -0
  52. airbyte_agent_jira/models.py +741 -0
  53. airbyte_agent_jira/types.py +117 -0
  54. airbyte_agent_jira-0.1.22.dist-info/METADATA +113 -0
  55. airbyte_agent_jira-0.1.22.dist-info/RECORD +56 -0
  56. airbyte_agent_jira-0.1.22.dist-info/WHEEL +4 -0
@@ -0,0 +1,978 @@
1
+ """
2
+ jira connector.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar, overload
9
+ try:
10
+ from typing import Literal
11
+ except ImportError:
12
+ from typing_extensions import Literal
13
+
14
+ from .connector_model import JiraConnectorModel
15
+ from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
16
+ from .types import (
17
+ IssueCommentsGetParams,
18
+ IssueCommentsListParams,
19
+ IssueFieldsListParams,
20
+ IssueFieldsSearchParams,
21
+ IssueWorklogsGetParams,
22
+ IssueWorklogsListParams,
23
+ IssuesGetParams,
24
+ IssuesSearchParams,
25
+ ProjectsGetParams,
26
+ ProjectsSearchParams,
27
+ UsersGetParams,
28
+ UsersListParams,
29
+ UsersSearchParams,
30
+ )
31
+ if TYPE_CHECKING:
32
+ from .models import JiraAuthConfig
33
+ # Import response models and envelope models at runtime
34
+ from .models import (
35
+ JiraExecuteResult,
36
+ JiraExecuteResultWithMeta,
37
+ IssuesSearchResult,
38
+ ProjectsSearchResult,
39
+ IssueCommentsListResult,
40
+ IssueWorklogsListResult,
41
+ Issue,
42
+ IssueComment,
43
+ IssueFieldSearchResults,
44
+ Project,
45
+ User,
46
+ Worklog,
47
+ )
48
+
49
+ # TypeVar for decorator type preservation
50
+ _F = TypeVar("_F", bound=Callable[..., Any])
51
+
52
+
53
+ class JiraConnector:
54
+ """
55
+ Type-safe Jira API connector.
56
+
57
+ Auto-generated from OpenAPI specification with full type safety.
58
+ """
59
+
60
+ connector_name = "jira"
61
+ connector_version = "1.0.3"
62
+ vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
63
+
64
+ # Map of (entity, action) -> has_extractors for envelope wrapping decision
65
+ _EXTRACTOR_MAP = {
66
+ ("issues", "search"): True,
67
+ ("issues", "get"): False,
68
+ ("projects", "search"): True,
69
+ ("projects", "get"): False,
70
+ ("users", "get"): False,
71
+ ("users", "list"): False,
72
+ ("users", "search"): False,
73
+ ("issue_fields", "list"): False,
74
+ ("issue_fields", "search"): False,
75
+ ("issue_comments", "list"): True,
76
+ ("issue_comments", "get"): False,
77
+ ("issue_worklogs", "list"): True,
78
+ ("issue_worklogs", "get"): False,
79
+ }
80
+
81
+ # Map of (entity, action) -> {python_param_name: api_param_name}
82
+ # Used to convert snake_case TypedDict keys to API parameter names in execute()
83
+ _PARAM_MAP = {
84
+ ('issues', 'search'): {'jql': 'jql', 'next_page_token': 'nextPageToken', 'max_results': 'maxResults', 'fields': 'fields', 'expand': 'expand', 'properties': 'properties', 'fields_by_keys': 'fieldsByKeys', 'fail_fast': 'failFast'},
85
+ ('issues', 'get'): {'issue_id_or_key': 'issueIdOrKey', 'fields': 'fields', 'expand': 'expand', 'properties': 'properties', 'fields_by_keys': 'fieldsByKeys', 'update_history': 'updateHistory', 'fail_fast': 'failFast'},
86
+ ('projects', 'search'): {'start_at': 'startAt', 'max_results': 'maxResults', 'order_by': 'orderBy', 'id': 'id', 'keys': 'keys', 'query': 'query', 'type_key': 'typeKey', 'category_id': 'categoryId', 'action': 'action', 'expand': 'expand', 'status': 'status'},
87
+ ('projects', 'get'): {'project_id_or_key': 'projectIdOrKey', 'expand': 'expand', 'properties': 'properties'},
88
+ ('users', 'get'): {'account_id': 'accountId', 'expand': 'expand'},
89
+ ('users', 'list'): {'start_at': 'startAt', 'max_results': 'maxResults'},
90
+ ('users', 'search'): {'query': 'query', 'start_at': 'startAt', 'max_results': 'maxResults', 'account_id': 'accountId', 'property': 'property'},
91
+ ('issue_fields', 'search'): {'start_at': 'startAt', 'max_results': 'maxResults', 'type': 'type', 'id': 'id', 'query': 'query', 'order_by': 'orderBy', 'expand': 'expand'},
92
+ ('issue_comments', 'list'): {'issue_id_or_key': 'issueIdOrKey', 'start_at': 'startAt', 'max_results': 'maxResults', 'order_by': 'orderBy', 'expand': 'expand'},
93
+ ('issue_comments', 'get'): {'issue_id_or_key': 'issueIdOrKey', 'comment_id': 'commentId', 'expand': 'expand'},
94
+ ('issue_worklogs', 'list'): {'issue_id_or_key': 'issueIdOrKey', 'start_at': 'startAt', 'max_results': 'maxResults', 'expand': 'expand'},
95
+ ('issue_worklogs', 'get'): {'issue_id_or_key': 'issueIdOrKey', 'worklog_id': 'worklogId', 'expand': 'expand'},
96
+ }
97
+
98
+ def __init__(
99
+ self,
100
+ auth_config: JiraAuthConfig | None = None,
101
+ external_user_id: str | None = None,
102
+ airbyte_client_id: str | None = None,
103
+ airbyte_client_secret: str | None = None,
104
+ on_token_refresh: Any | None = None,
105
+ subdomain: str | None = None ):
106
+ """
107
+ Initialize a new jira connector instance.
108
+
109
+ Supports both local and hosted execution modes:
110
+ - Local mode: Provide `auth_config` for direct API calls
111
+ - Hosted mode: Provide `external_user_id`, `airbyte_client_id`, and `airbyte_client_secret` for hosted execution
112
+
113
+ Args:
114
+ auth_config: Typed authentication configuration (required for local mode)
115
+ external_user_id: External user ID (required for hosted mode)
116
+ airbyte_client_id: Airbyte OAuth client ID (required for hosted mode)
117
+ airbyte_client_secret: Airbyte OAuth client secret (required for hosted mode)
118
+ on_token_refresh: Optional callback for OAuth2 token refresh persistence.
119
+ Called with new_tokens dict when tokens are refreshed. Can be sync or async.
120
+ Example: lambda tokens: save_to_database(tokens) subdomain: Your Jira Cloud subdomain
121
+ Examples:
122
+ # Local mode (direct API calls)
123
+ connector = JiraConnector(auth_config=JiraAuthConfig(username="...", password="..."))
124
+ # Hosted mode (executed on Airbyte cloud)
125
+ connector = JiraConnector(
126
+ external_user_id="user-123",
127
+ airbyte_client_id="client_abc123",
128
+ airbyte_client_secret="secret_xyz789"
129
+ )
130
+
131
+ # Local mode with OAuth2 token refresh callback
132
+ def save_tokens(new_tokens: dict) -> None:
133
+ # Persist updated tokens to your storage (file, database, etc.)
134
+ with open("tokens.json", "w") as f:
135
+ json.dump(new_tokens, f)
136
+
137
+ connector = JiraConnector(
138
+ auth_config=JiraAuthConfig(access_token="...", refresh_token="..."),
139
+ on_token_refresh=save_tokens
140
+ )
141
+ """
142
+ # Hosted mode: external_user_id, airbyte_client_id, and airbyte_client_secret provided
143
+ if external_user_id and airbyte_client_id and airbyte_client_secret:
144
+ from ._vendored.connector_sdk.executor import HostedExecutor
145
+ self._executor = HostedExecutor(
146
+ external_user_id=external_user_id,
147
+ airbyte_client_id=airbyte_client_id,
148
+ airbyte_client_secret=airbyte_client_secret,
149
+ connector_definition_id=str(JiraConnectorModel.id),
150
+ )
151
+ else:
152
+ # Local mode: auth_config required
153
+ if not auth_config:
154
+ raise ValueError(
155
+ "Either provide (external_user_id, airbyte_client_id, airbyte_client_secret) for hosted mode "
156
+ "or auth_config for local mode"
157
+ )
158
+
159
+ from ._vendored.connector_sdk.executor import LocalExecutor
160
+
161
+ # Build config_values dict from server variables
162
+ config_values: dict[str, str] = {}
163
+ if subdomain:
164
+ config_values["subdomain"] = subdomain
165
+
166
+ self._executor = LocalExecutor(
167
+ model=JiraConnectorModel,
168
+ auth_config=auth_config.model_dump() if auth_config else None,
169
+ config_values=config_values,
170
+ on_token_refresh=on_token_refresh
171
+ )
172
+
173
+ # Update base_url with server variables if provided
174
+ base_url = self._executor.http_client.base_url
175
+ if subdomain:
176
+ base_url = base_url.replace("{subdomain}", subdomain)
177
+ self._executor.http_client.base_url = base_url
178
+
179
+ # Initialize entity query objects
180
+ self.issues = IssuesQuery(self)
181
+ self.projects = ProjectsQuery(self)
182
+ self.users = UsersQuery(self)
183
+ self.issue_fields = IssueFieldsQuery(self)
184
+ self.issue_comments = IssueCommentsQuery(self)
185
+ self.issue_worklogs = IssueWorklogsQuery(self)
186
+
187
+ # ===== TYPED EXECUTE METHOD (Recommended Interface) =====
188
+
189
+ @overload
190
+ async def execute(
191
+ self,
192
+ entity: Literal["issues"],
193
+ action: Literal["search"],
194
+ params: "IssuesSearchParams"
195
+ ) -> "IssuesSearchResult": ...
196
+
197
+ @overload
198
+ async def execute(
199
+ self,
200
+ entity: Literal["issues"],
201
+ action: Literal["get"],
202
+ params: "IssuesGetParams"
203
+ ) -> "Issue": ...
204
+
205
+ @overload
206
+ async def execute(
207
+ self,
208
+ entity: Literal["projects"],
209
+ action: Literal["search"],
210
+ params: "ProjectsSearchParams"
211
+ ) -> "ProjectsSearchResult": ...
212
+
213
+ @overload
214
+ async def execute(
215
+ self,
216
+ entity: Literal["projects"],
217
+ action: Literal["get"],
218
+ params: "ProjectsGetParams"
219
+ ) -> "Project": ...
220
+
221
+ @overload
222
+ async def execute(
223
+ self,
224
+ entity: Literal["users"],
225
+ action: Literal["get"],
226
+ params: "UsersGetParams"
227
+ ) -> "User": ...
228
+
229
+ @overload
230
+ async def execute(
231
+ self,
232
+ entity: Literal["users"],
233
+ action: Literal["list"],
234
+ params: "UsersListParams"
235
+ ) -> "dict[str, Any]": ...
236
+
237
+ @overload
238
+ async def execute(
239
+ self,
240
+ entity: Literal["users"],
241
+ action: Literal["search"],
242
+ params: "UsersSearchParams"
243
+ ) -> "dict[str, Any]": ...
244
+
245
+ @overload
246
+ async def execute(
247
+ self,
248
+ entity: Literal["issue_fields"],
249
+ action: Literal["list"],
250
+ params: "IssueFieldsListParams"
251
+ ) -> "dict[str, Any]": ...
252
+
253
+ @overload
254
+ async def execute(
255
+ self,
256
+ entity: Literal["issue_fields"],
257
+ action: Literal["search"],
258
+ params: "IssueFieldsSearchParams"
259
+ ) -> "IssueFieldSearchResults": ...
260
+
261
+ @overload
262
+ async def execute(
263
+ self,
264
+ entity: Literal["issue_comments"],
265
+ action: Literal["list"],
266
+ params: "IssueCommentsListParams"
267
+ ) -> "IssueCommentsListResult": ...
268
+
269
+ @overload
270
+ async def execute(
271
+ self,
272
+ entity: Literal["issue_comments"],
273
+ action: Literal["get"],
274
+ params: "IssueCommentsGetParams"
275
+ ) -> "IssueComment": ...
276
+
277
+ @overload
278
+ async def execute(
279
+ self,
280
+ entity: Literal["issue_worklogs"],
281
+ action: Literal["list"],
282
+ params: "IssueWorklogsListParams"
283
+ ) -> "IssueWorklogsListResult": ...
284
+
285
+ @overload
286
+ async def execute(
287
+ self,
288
+ entity: Literal["issue_worklogs"],
289
+ action: Literal["get"],
290
+ params: "IssueWorklogsGetParams"
291
+ ) -> "Worklog": ...
292
+
293
+
294
+ @overload
295
+ async def execute(
296
+ self,
297
+ entity: str,
298
+ action: str,
299
+ params: dict[str, Any]
300
+ ) -> JiraExecuteResult[Any] | JiraExecuteResultWithMeta[Any, Any] | Any: ...
301
+
302
+ async def execute(
303
+ self,
304
+ entity: str,
305
+ action: str,
306
+ params: dict[str, Any] | None = None
307
+ ) -> Any:
308
+ """
309
+ Execute an entity operation with full type safety.
310
+
311
+ This is the recommended interface for blessed connectors as it:
312
+ - Uses the same signature as non-blessed connectors
313
+ - Provides full IDE autocomplete for entity/action/params
314
+ - Makes migration from generic to blessed connectors seamless
315
+
316
+ Args:
317
+ entity: Entity name (e.g., "customers")
318
+ action: Operation action (e.g., "create", "get", "list")
319
+ params: Operation parameters (typed based on entity+action)
320
+
321
+ Returns:
322
+ Typed response based on the operation
323
+
324
+ Example:
325
+ customer = await connector.execute(
326
+ entity="customers",
327
+ action="get",
328
+ params={"id": "cus_123"}
329
+ )
330
+ """
331
+ from ._vendored.connector_sdk.executor import ExecutionConfig
332
+
333
+ # Remap parameter names from snake_case (TypedDict keys) to API parameter names
334
+ if params:
335
+ param_map = self._PARAM_MAP.get((entity, action), {})
336
+ if param_map:
337
+ params = {param_map.get(k, k): v for k, v in params.items()}
338
+
339
+ # Use ExecutionConfig for both local and hosted executors
340
+ config = ExecutionConfig(
341
+ entity=entity,
342
+ action=action,
343
+ params=params
344
+ )
345
+
346
+ result = await self._executor.execute(config)
347
+
348
+ if not result.success:
349
+ raise RuntimeError(f"Execution failed: {result.error}")
350
+
351
+ # Check if this operation has extractors configured
352
+ has_extractors = self._EXTRACTOR_MAP.get((entity, action), False)
353
+
354
+ if has_extractors:
355
+ # With extractors - return Pydantic envelope with data and meta
356
+ if result.meta is not None:
357
+ return JiraExecuteResultWithMeta[Any, Any](
358
+ data=result.data,
359
+ meta=result.meta
360
+ )
361
+ else:
362
+ return JiraExecuteResult[Any](data=result.data)
363
+ else:
364
+ # No extractors - return raw response data
365
+ return result.data
366
+
367
+ # ===== INTROSPECTION METHODS =====
368
+
369
+ @classmethod
370
+ def describe(cls, func: _F) -> _F:
371
+ """
372
+ Decorator that populates a function's docstring with connector capabilities.
373
+
374
+ This class method can be used as a decorator to automatically generate
375
+ comprehensive documentation for AI tool functions.
376
+
377
+ Usage:
378
+ @mcp.tool()
379
+ @JiraConnector.describe
380
+ async def execute(entity: str, action: str, params: dict):
381
+ '''Execute operations.'''
382
+ ...
383
+
384
+ The decorated function's __doc__ will be updated with:
385
+ - Available entities and their actions
386
+ - Parameter signatures with required (*) and optional (?) markers
387
+ - Response structure documentation
388
+ - Example questions (if available in OpenAPI spec)
389
+
390
+ Args:
391
+ func: The function to decorate
392
+
393
+ Returns:
394
+ The same function with updated __doc__
395
+ """
396
+ description = generate_tool_description(JiraConnectorModel)
397
+
398
+ original_doc = func.__doc__ or ""
399
+ if original_doc.strip():
400
+ func.__doc__ = f"{original_doc.strip()}\n\n{description}"
401
+ else:
402
+ func.__doc__ = description
403
+
404
+ return func
405
+
406
+ def list_entities(self) -> list[dict[str, Any]]:
407
+ """
408
+ Get structured data about available entities, actions, and parameters.
409
+
410
+ Returns a list of entity descriptions with:
411
+ - entity_name: Name of the entity (e.g., "contacts", "deals")
412
+ - description: Entity description from the first endpoint
413
+ - available_actions: List of actions (e.g., ["list", "get", "create"])
414
+ - parameters: Dict mapping action -> list of parameter dicts
415
+
416
+ Example:
417
+ entities = connector.list_entities()
418
+ for entity in entities:
419
+ print(f"{entity['entity_name']}: {entity['available_actions']}")
420
+ """
421
+ return describe_entities(JiraConnectorModel)
422
+
423
+ def entity_schema(self, entity: str) -> dict[str, Any] | None:
424
+ """
425
+ Get the JSON schema for an entity.
426
+
427
+ Args:
428
+ entity: Entity name (e.g., "contacts", "companies")
429
+
430
+ Returns:
431
+ JSON schema dict describing the entity structure, or None if not found.
432
+
433
+ Example:
434
+ schema = connector.entity_schema("contacts")
435
+ if schema:
436
+ print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
437
+ """
438
+ entity_def = next(
439
+ (e for e in JiraConnectorModel.entities if e.name == entity),
440
+ None
441
+ )
442
+ if entity_def is None:
443
+ logging.getLogger(__name__).warning(
444
+ f"Entity '{entity}' not found. Available entities: "
445
+ f"{[e.name for e in JiraConnectorModel.entities]}"
446
+ )
447
+ return entity_def.entity_schema if entity_def else None
448
+
449
+
450
+
451
+ class IssuesQuery:
452
+ """
453
+ Query class for Issues entity operations.
454
+ """
455
+
456
+ def __init__(self, connector: JiraConnector):
457
+ """Initialize query with connector reference."""
458
+ self._connector = connector
459
+
460
+ async def search(
461
+ self,
462
+ jql: str | None = None,
463
+ next_page_token: str | None = None,
464
+ max_results: int | None = None,
465
+ fields: str | None = None,
466
+ expand: str | None = None,
467
+ properties: str | None = None,
468
+ fields_by_keys: bool | None = None,
469
+ fail_fast: bool | None = None,
470
+ **kwargs
471
+ ) -> IssuesSearchResult:
472
+ """
473
+ Retrieve issues based on JQL query with pagination support
474
+
475
+ Args:
476
+ jql: JQL query string to filter issues
477
+ next_page_token: The token for a page to fetch that is not the first page. The first page has a nextPageToken of null. Use the `nextPageToken` to fetch the next page of issues. The `nextPageToken` field is not included in the response for the last page, indicating there is no next page.
478
+ max_results: The maximum number of items to return per page. To manage page size, API may return fewer items per page where a large number of fields or properties are requested. The greatest number of items returned per page is achieved when requesting `id` or `key` only. It returns max 5000 issues.
479
+ fields: A comma-separated list of fields to return for each issue. By default, all navigable fields are returned. To get a list of all fields, use the Get fields operation.
480
+ expand: A comma-separated list of parameters to expand. This parameter accepts multiple values, including `renderedFields`, `names`, `schema`, `transitions`, `operations`, `editmeta`, `changelog`, and `versionedRepresentations`.
481
+ properties: A comma-separated list of issue property keys. To get a list of all issue property keys, use the Get issue operation. A maximum of 5 properties can be requested.
482
+ fields_by_keys: Whether the fields parameter contains field keys (true) or field IDs (false). Default is false.
483
+ fail_fast: Fail the request early if all field data cannot be retrieved. Default is false.
484
+ **kwargs: Additional parameters
485
+
486
+ Returns:
487
+ IssuesSearchResult
488
+ """
489
+ params = {k: v for k, v in {
490
+ "jql": jql,
491
+ "nextPageToken": next_page_token,
492
+ "maxResults": max_results,
493
+ "fields": fields,
494
+ "expand": expand,
495
+ "properties": properties,
496
+ "fieldsByKeys": fields_by_keys,
497
+ "failFast": fail_fast,
498
+ **kwargs
499
+ }.items() if v is not None}
500
+
501
+ result = await self._connector.execute("issues", "search", params)
502
+ # Cast generic envelope to concrete typed result
503
+ return IssuesSearchResult(
504
+ data=result.data,
505
+ meta=result.meta )
506
+
507
+
508
+
509
+ async def get(
510
+ self,
511
+ issue_id_or_key: str,
512
+ fields: str | None = None,
513
+ expand: str | None = None,
514
+ properties: str | None = None,
515
+ fields_by_keys: bool | None = None,
516
+ update_history: bool | None = None,
517
+ fail_fast: bool | None = None,
518
+ **kwargs
519
+ ) -> Issue:
520
+ """
521
+ Retrieve a single issue by its ID or key
522
+
523
+ Args:
524
+ issue_id_or_key: The issue ID or key (e.g., "PROJ-123" or "10000")
525
+ fields: A comma-separated list of fields to return for the issue. By default, all navigable and Jira default fields are returned. Use it to retrieve a subset of fields.
526
+ expand: A comma-separated list of parameters to expand. This parameter accepts multiple values, including `renderedFields`, `names`, `schema`, `transitions`, `operations`, `editmeta`, `changelog`, and `versionedRepresentations`.
527
+ properties: A comma-separated list of issue property keys. To get a list of all issue property keys, use the Get issue operation. A maximum of 5 properties can be requested.
528
+ fields_by_keys: Whether the fields parameter contains field keys (true) or field IDs (false). Default is false.
529
+ update_history: Whether the action taken is added to the user's Recent history. Default is false.
530
+ fail_fast: Fail the request early if all field data cannot be retrieved. Default is false.
531
+ **kwargs: Additional parameters
532
+
533
+ Returns:
534
+ Issue
535
+ """
536
+ params = {k: v for k, v in {
537
+ "issueIdOrKey": issue_id_or_key,
538
+ "fields": fields,
539
+ "expand": expand,
540
+ "properties": properties,
541
+ "fieldsByKeys": fields_by_keys,
542
+ "updateHistory": update_history,
543
+ "failFast": fail_fast,
544
+ **kwargs
545
+ }.items() if v is not None}
546
+
547
+ result = await self._connector.execute("issues", "get", params)
548
+ return result
549
+
550
+
551
+
552
+ class ProjectsQuery:
553
+ """
554
+ Query class for Projects entity operations.
555
+ """
556
+
557
+ def __init__(self, connector: JiraConnector):
558
+ """Initialize query with connector reference."""
559
+ self._connector = connector
560
+
561
+ async def search(
562
+ self,
563
+ start_at: int | None = None,
564
+ max_results: int | None = None,
565
+ order_by: str | None = None,
566
+ id: list[int] | None = None,
567
+ keys: list[str] | None = None,
568
+ query: str | None = None,
569
+ type_key: str | None = None,
570
+ category_id: int | None = None,
571
+ action: str | None = None,
572
+ expand: str | None = None,
573
+ status: list[str] | None = None,
574
+ **kwargs
575
+ ) -> ProjectsSearchResult:
576
+ """
577
+ Search and filter projects with advanced query parameters
578
+
579
+ Args:
580
+ start_at: The index of the first item to return in a page of results (page offset)
581
+ max_results: The maximum number of items to return per page (max 100)
582
+ order_by: Order the results by a field (prefix with + for ascending, - for descending)
583
+ id: Filter by project IDs (up to 50)
584
+ keys: Filter by project keys (up to 50)
585
+ query: Filter using a literal string (matches project key or name, case insensitive)
586
+ type_key: Filter by project type (comma-separated)
587
+ category_id: Filter by project category ID
588
+ action: Filter by user permission (view, browse, edit, create)
589
+ expand: Comma-separated list of additional fields (description, projectKeys, lead, issueTypes, url, insight)
590
+ status: EXPERIMENTAL - Filter by project status
591
+ **kwargs: Additional parameters
592
+
593
+ Returns:
594
+ ProjectsSearchResult
595
+ """
596
+ params = {k: v for k, v in {
597
+ "startAt": start_at,
598
+ "maxResults": max_results,
599
+ "orderBy": order_by,
600
+ "id": id,
601
+ "keys": keys,
602
+ "query": query,
603
+ "typeKey": type_key,
604
+ "categoryId": category_id,
605
+ "action": action,
606
+ "expand": expand,
607
+ "status": status,
608
+ **kwargs
609
+ }.items() if v is not None}
610
+
611
+ result = await self._connector.execute("projects", "search", params)
612
+ # Cast generic envelope to concrete typed result
613
+ return ProjectsSearchResult(
614
+ data=result.data,
615
+ meta=result.meta )
616
+
617
+
618
+
619
+ async def get(
620
+ self,
621
+ project_id_or_key: str,
622
+ expand: str | None = None,
623
+ properties: str | None = None,
624
+ **kwargs
625
+ ) -> Project:
626
+ """
627
+ Retrieve a single project by its ID or key
628
+
629
+ Args:
630
+ project_id_or_key: The project ID or key (e.g., "PROJ" or "10000")
631
+ expand: Comma-separated list of additional fields to include (description, projectKeys, lead, issueTypes, url, insight)
632
+ properties: A comma-separated list of project property keys to return. To get a list of all project property keys, use Get project property keys.
633
+ **kwargs: Additional parameters
634
+
635
+ Returns:
636
+ Project
637
+ """
638
+ params = {k: v for k, v in {
639
+ "projectIdOrKey": project_id_or_key,
640
+ "expand": expand,
641
+ "properties": properties,
642
+ **kwargs
643
+ }.items() if v is not None}
644
+
645
+ result = await self._connector.execute("projects", "get", params)
646
+ return result
647
+
648
+
649
+
650
+ class UsersQuery:
651
+ """
652
+ Query class for Users entity operations.
653
+ """
654
+
655
+ def __init__(self, connector: JiraConnector):
656
+ """Initialize query with connector reference."""
657
+ self._connector = connector
658
+
659
+ async def get(
660
+ self,
661
+ account_id: str,
662
+ expand: str | None = None,
663
+ **kwargs
664
+ ) -> User:
665
+ """
666
+ Retrieve a single user by their account ID
667
+
668
+ Args:
669
+ account_id: The account ID of the user
670
+ expand: Comma-separated list of additional fields to include (groups, applicationRoles)
671
+ **kwargs: Additional parameters
672
+
673
+ Returns:
674
+ User
675
+ """
676
+ params = {k: v for k, v in {
677
+ "accountId": account_id,
678
+ "expand": expand,
679
+ **kwargs
680
+ }.items() if v is not None}
681
+
682
+ result = await self._connector.execute("users", "get", params)
683
+ return result
684
+
685
+
686
+
687
+ async def list(
688
+ self,
689
+ start_at: int | None = None,
690
+ max_results: int | None = None,
691
+ **kwargs
692
+ ) -> dict[str, Any]:
693
+ """
694
+ Returns a paginated list of users
695
+
696
+ Args:
697
+ start_at: The index of the first item to return in a page of results (page offset)
698
+ max_results: The maximum number of items to return per page (max 1000)
699
+ **kwargs: Additional parameters
700
+
701
+ Returns:
702
+ dict[str, Any]
703
+ """
704
+ params = {k: v for k, v in {
705
+ "startAt": start_at,
706
+ "maxResults": max_results,
707
+ **kwargs
708
+ }.items() if v is not None}
709
+
710
+ result = await self._connector.execute("users", "list", params)
711
+ return result
712
+
713
+
714
+
715
+ async def search(
716
+ self,
717
+ query: str | None = None,
718
+ start_at: int | None = None,
719
+ max_results: int | None = None,
720
+ account_id: str | None = None,
721
+ property: str | None = None,
722
+ **kwargs
723
+ ) -> dict[str, Any]:
724
+ """
725
+ Search for users using a query string
726
+
727
+ Args:
728
+ query: A query string to search for users (matches display name, email, account ID)
729
+ start_at: The index of the first item to return in a page of results (page offset)
730
+ max_results: The maximum number of items to return per page (max 1000)
731
+ account_id: Filter by account IDs (supports multiple values)
732
+ property: Property key to filter users
733
+ **kwargs: Additional parameters
734
+
735
+ Returns:
736
+ dict[str, Any]
737
+ """
738
+ params = {k: v for k, v in {
739
+ "query": query,
740
+ "startAt": start_at,
741
+ "maxResults": max_results,
742
+ "accountId": account_id,
743
+ "property": property,
744
+ **kwargs
745
+ }.items() if v is not None}
746
+
747
+ result = await self._connector.execute("users", "search", params)
748
+ return result
749
+
750
+
751
+
752
+ class IssueFieldsQuery:
753
+ """
754
+ Query class for IssueFields entity operations.
755
+ """
756
+
757
+ def __init__(self, connector: JiraConnector):
758
+ """Initialize query with connector reference."""
759
+ self._connector = connector
760
+
761
+ async def list(
762
+ self,
763
+ **kwargs
764
+ ) -> dict[str, Any]:
765
+ """
766
+ Returns a list of all custom and system fields
767
+
768
+ Returns:
769
+ dict[str, Any]
770
+ """
771
+ params = {k: v for k, v in {
772
+ **kwargs
773
+ }.items() if v is not None}
774
+
775
+ result = await self._connector.execute("issue_fields", "list", params)
776
+ return result
777
+
778
+
779
+
780
+ async def search(
781
+ self,
782
+ start_at: int | None = None,
783
+ max_results: int | None = None,
784
+ type: list[str] | None = None,
785
+ id: list[str] | None = None,
786
+ query: str | None = None,
787
+ order_by: str | None = None,
788
+ expand: str | None = None,
789
+ **kwargs
790
+ ) -> IssueFieldSearchResults:
791
+ """
792
+ Search and filter issue fields with query parameters
793
+
794
+ Args:
795
+ start_at: The index of the first item to return in a page of results (page offset)
796
+ max_results: The maximum number of items to return per page (max 100)
797
+ type: The type of fields to search for (custom, system, or both)
798
+ id: List of field IDs to search for
799
+ query: String to match against field names, descriptions, and field IDs (case insensitive)
800
+ order_by: Order the results by a field (contextsCount, lastUsed, name, screensCount)
801
+ expand: Comma-separated list of additional fields to include (searcherKey, screensCount, contextsCount, isLocked, lastUsed)
802
+ **kwargs: Additional parameters
803
+
804
+ Returns:
805
+ IssueFieldSearchResults
806
+ """
807
+ params = {k: v for k, v in {
808
+ "startAt": start_at,
809
+ "maxResults": max_results,
810
+ "type": type,
811
+ "id": id,
812
+ "query": query,
813
+ "orderBy": order_by,
814
+ "expand": expand,
815
+ **kwargs
816
+ }.items() if v is not None}
817
+
818
+ result = await self._connector.execute("issue_fields", "search", params)
819
+ return result
820
+
821
+
822
+
823
+ class IssueCommentsQuery:
824
+ """
825
+ Query class for IssueComments entity operations.
826
+ """
827
+
828
+ def __init__(self, connector: JiraConnector):
829
+ """Initialize query with connector reference."""
830
+ self._connector = connector
831
+
832
+ async def list(
833
+ self,
834
+ issue_id_or_key: str,
835
+ start_at: int | None = None,
836
+ max_results: int | None = None,
837
+ order_by: str | None = None,
838
+ expand: str | None = None,
839
+ **kwargs
840
+ ) -> IssueCommentsListResult:
841
+ """
842
+ Retrieve all comments for a specific issue
843
+
844
+ Args:
845
+ issue_id_or_key: The issue ID or key (e.g., "PROJ-123" or "10000")
846
+ start_at: The index of the first item to return in a page of results (page offset)
847
+ max_results: The maximum number of items to return per page
848
+ order_by: Order the results by created date (+ for ascending, - for descending)
849
+ expand: Comma-separated list of additional fields to include (renderedBody, properties)
850
+ **kwargs: Additional parameters
851
+
852
+ Returns:
853
+ IssueCommentsListResult
854
+ """
855
+ params = {k: v for k, v in {
856
+ "issueIdOrKey": issue_id_or_key,
857
+ "startAt": start_at,
858
+ "maxResults": max_results,
859
+ "orderBy": order_by,
860
+ "expand": expand,
861
+ **kwargs
862
+ }.items() if v is not None}
863
+
864
+ result = await self._connector.execute("issue_comments", "list", params)
865
+ # Cast generic envelope to concrete typed result
866
+ return IssueCommentsListResult(
867
+ data=result.data,
868
+ meta=result.meta )
869
+
870
+
871
+
872
+ async def get(
873
+ self,
874
+ issue_id_or_key: str,
875
+ comment_id: str,
876
+ expand: str | None = None,
877
+ **kwargs
878
+ ) -> IssueComment:
879
+ """
880
+ Retrieve a single comment by its ID
881
+
882
+ Args:
883
+ issue_id_or_key: The issue ID or key (e.g., "PROJ-123" or "10000")
884
+ comment_id: The comment ID
885
+ expand: Comma-separated list of additional fields to include (renderedBody, properties)
886
+ **kwargs: Additional parameters
887
+
888
+ Returns:
889
+ IssueComment
890
+ """
891
+ params = {k: v for k, v in {
892
+ "issueIdOrKey": issue_id_or_key,
893
+ "commentId": comment_id,
894
+ "expand": expand,
895
+ **kwargs
896
+ }.items() if v is not None}
897
+
898
+ result = await self._connector.execute("issue_comments", "get", params)
899
+ return result
900
+
901
+
902
+
903
+ class IssueWorklogsQuery:
904
+ """
905
+ Query class for IssueWorklogs entity operations.
906
+ """
907
+
908
+ def __init__(self, connector: JiraConnector):
909
+ """Initialize query with connector reference."""
910
+ self._connector = connector
911
+
912
+ async def list(
913
+ self,
914
+ issue_id_or_key: str,
915
+ start_at: int | None = None,
916
+ max_results: int | None = None,
917
+ expand: str | None = None,
918
+ **kwargs
919
+ ) -> IssueWorklogsListResult:
920
+ """
921
+ Retrieve all worklogs for a specific issue
922
+
923
+ Args:
924
+ issue_id_or_key: The issue ID or key (e.g., "PROJ-123" or "10000")
925
+ start_at: The index of the first item to return in a page of results (page offset)
926
+ max_results: The maximum number of items to return per page
927
+ expand: Comma-separated list of additional fields to include (properties)
928
+ **kwargs: Additional parameters
929
+
930
+ Returns:
931
+ IssueWorklogsListResult
932
+ """
933
+ params = {k: v for k, v in {
934
+ "issueIdOrKey": issue_id_or_key,
935
+ "startAt": start_at,
936
+ "maxResults": max_results,
937
+ "expand": expand,
938
+ **kwargs
939
+ }.items() if v is not None}
940
+
941
+ result = await self._connector.execute("issue_worklogs", "list", params)
942
+ # Cast generic envelope to concrete typed result
943
+ return IssueWorklogsListResult(
944
+ data=result.data,
945
+ meta=result.meta )
946
+
947
+
948
+
949
+ async def get(
950
+ self,
951
+ issue_id_or_key: str,
952
+ worklog_id: str,
953
+ expand: str | None = None,
954
+ **kwargs
955
+ ) -> Worklog:
956
+ """
957
+ Retrieve a single worklog by its ID
958
+
959
+ Args:
960
+ issue_id_or_key: The issue ID or key (e.g., "PROJ-123" or "10000")
961
+ worklog_id: The worklog ID
962
+ expand: Comma-separated list of additional fields to include (properties)
963
+ **kwargs: Additional parameters
964
+
965
+ Returns:
966
+ Worklog
967
+ """
968
+ params = {k: v for k, v in {
969
+ "issueIdOrKey": issue_id_or_key,
970
+ "worklogId": worklog_id,
971
+ "expand": expand,
972
+ **kwargs
973
+ }.items() if v is not None}
974
+
975
+ result = await self._connector.execute("issue_worklogs", "get", params)
976
+ return result
977
+
978
+