|
24 | 24 | CommentCreateRequest, |
25 | 25 | CommentRequest, |
26 | 26 | CommentsRequest, |
| 27 | + FindLabelRequest, |
| 28 | + FindProjectRequest, |
| 29 | + FindTeamRequest, |
| 30 | + FindUserRequest, |
27 | 31 | FindWorkflowStateRequest, |
28 | 32 | IssueAddLabelRequest, |
29 | 33 | IssueArchiveRequest, |
|
52 | 56 | CreateCommentResponse, |
53 | 57 | CreateIssueResponse, |
54 | 58 | IssueDetailsResponse, |
| 59 | + IssueLabelResponse, |
55 | 60 | IssueLabelsResponse, |
56 | 61 | IssueResponse, |
57 | 62 | IssuesResponse, |
@@ -83,6 +88,12 @@ def _to_int(value: str | None) -> int | None: |
83 | 88 | return None |
84 | 89 |
|
85 | 90 |
|
| 91 | +def _first_node(data: dict[str, Any], key: str) -> dict[str, Any] | None: |
| 92 | + """Return the first node of a connection in ``data[key]``, or ``None``.""" |
| 93 | + nodes = (data.get(key) or {}).get("nodes") or [] |
| 94 | + return nodes[0] if nodes else None |
| 95 | + |
| 96 | + |
86 | 97 | class LinearClient: |
87 | 98 | """Client for Linear's GraphQL API. |
88 | 99 |
|
@@ -556,17 +567,66 @@ def find_workflow_state(self, request: FindWorkflowStateRequest) -> WorkflowStat |
556 | 567 | A [`WorkflowStateResponse`][linear_python_client.WorkflowStateResponse]; |
557 | 568 | `.state` is `None` if no state matches. |
558 | 569 | """ |
559 | | - variables = { |
560 | | - "first": 1, |
561 | | - "after": None, |
562 | | - "filter": { |
563 | | - "team": {"id": {"eq": request.team_id}}, |
564 | | - "name": {"eqIgnoreCase": request.name}, |
565 | | - }, |
| 570 | + filter_ = { |
| 571 | + "team": {"id": {"eq": request.team_id}}, |
| 572 | + "name": {"eqIgnoreCase": request.name}, |
566 | 573 | } |
567 | | - data = self.execute(queries.WORKFLOW_STATES, variables) |
568 | | - nodes = (data.get("workflowStates") or {}).get("nodes") or [] |
569 | | - return WorkflowStateResponse.model_validate({"state": nodes[0] if nodes else None}) |
| 574 | + data = self.execute(queries.WORKFLOW_STATES, {"first": 1, "filter": filter_}) |
| 575 | + return WorkflowStateResponse.model_validate({"state": _first_node(data, "workflowStates")}) |
| 576 | + |
| 577 | + def find_team(self, request: FindTeamRequest) -> TeamResponse: |
| 578 | + """Resolve a team by display name or key. |
| 579 | +
|
| 580 | + Args: |
| 581 | + request: A [`FindTeamRequest`][linear_python_client.FindTeamRequest] |
| 582 | + with `name` and/or `key`. |
| 583 | +
|
| 584 | + Returns: |
| 585 | + A [`TeamResponse`][linear_python_client.TeamResponse]; `.team` is |
| 586 | + `None` if no team matches. |
| 587 | + """ |
| 588 | + data = self.execute(queries.TEAMS, {"first": 1, "filter": request.to_filter()}) |
| 589 | + return TeamResponse.model_validate({"team": _first_node(data, "teams")}) |
| 590 | + |
| 591 | + def find_user(self, request: FindUserRequest) -> UserResponse: |
| 592 | + """Resolve a user by name, display name, or email. |
| 593 | +
|
| 594 | + Args: |
| 595 | + request: A [`FindUserRequest`][linear_python_client.FindUserRequest] |
| 596 | + with `name` and/or `email`. |
| 597 | +
|
| 598 | + Returns: |
| 599 | + A [`UserResponse`][linear_python_client.UserResponse]; `.user` is |
| 600 | + `None` if no user matches. |
| 601 | + """ |
| 602 | + data = self.execute(queries.USERS, {"first": 1, "filter": request.to_filter()}) |
| 603 | + return UserResponse.model_validate({"user": _first_node(data, "users")}) |
| 604 | + |
| 605 | + def find_project(self, request: FindProjectRequest) -> ProjectResponse: |
| 606 | + """Resolve a project by name (case-insensitive). |
| 607 | +
|
| 608 | + Args: |
| 609 | + request: A [`FindProjectRequest`][linear_python_client.FindProjectRequest]. |
| 610 | +
|
| 611 | + Returns: |
| 612 | + A [`ProjectResponse`][linear_python_client.ProjectResponse]; `.project` |
| 613 | + is `None` if no project matches. |
| 614 | + """ |
| 615 | + data = self.execute(queries.PROJECTS, {"first": 1, "filter": request.to_filter()}) |
| 616 | + return ProjectResponse.model_validate({"project": _first_node(data, "projects")}) |
| 617 | + |
| 618 | + def find_label(self, request: FindLabelRequest) -> IssueLabelResponse: |
| 619 | + """Resolve an issue label by name, optionally scoped to a team. |
| 620 | +
|
| 621 | + Args: |
| 622 | + request: A [`FindLabelRequest`][linear_python_client.FindLabelRequest]. |
| 623 | +
|
| 624 | + Returns: |
| 625 | + An [`IssueLabelResponse`][linear_python_client.IssueLabelResponse]; |
| 626 | + `.label` is `None` if no label matches. |
| 627 | + """ |
| 628 | + data = self.execute(queries.ISSUE_LABELS, {"first": 1, "filter": request.to_filter()}) |
| 629 | + return IssueLabelResponse.model_validate({"label": _first_node(data, "issueLabels")}) |
570 | 630 |
|
571 | 631 | def issue_labels(self, request: IssueLabelsRequest | None = None) -> IssueLabelsResponse: |
572 | 632 | """List issue labels in the workspace. |
|
0 commit comments