pull-request-fixer 0.1.1__tar.gz → 0.1.2__tar.gz

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 (24) hide show
  1. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/PKG-INFO +21 -19
  2. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/README.md +4 -4
  3. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/pyproject.toml +92 -16
  4. pull_request_fixer-0.1.2/src/pull_request_fixer/_version.py +24 -0
  5. pull_request_fixer-0.1.2/src/pull_request_fixer/blocked_pr_scanner.py +216 -0
  6. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/cli.py +175 -71
  7. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/exceptions.py +6 -0
  8. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/git_config.py +1 -1
  9. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/github_client.py +15 -4
  10. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/pr_file_fixer.py +143 -30
  11. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/pr_scanner.py +56 -14
  12. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/progress_tracker.py +8 -8
  13. pull_request_fixer-0.1.1/src/pull_request_fixer/_version.py +0 -34
  14. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/.gitignore +0 -0
  15. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/.pre-commit-hooks.yaml +0 -0
  16. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/LICENSE +0 -0
  17. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/LICENSES/Apache-2.0.txt +0 -0
  18. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/scripts/README.md +0 -0
  19. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/__init__.py +0 -0
  20. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/file_fixer.py +0 -0
  21. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/graphql_queries.py +0 -0
  22. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/models.py +0 -0
  23. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/pr_fixer.py +0 -0
  24. {pull_request_fixer-0.1.1 → pull_request_fixer-0.1.2}/src/pull_request_fixer/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pull-request-fixer
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Pull request formatter and fixer with GitHub integration
5
5
  Project-URL: Homepage, https://github.com/lfit/pull-request-fixer
6
6
  Project-URL: Repository, https://github.com/lfit/pull-request-fixer.git
@@ -19,23 +19,25 @@ Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
22
23
  Classifier: Topic :: Software Development :: Quality Assurance
23
24
  Classifier: Topic :: Text Processing :: Markup :: Markdown
24
- Requires-Python: <3.14,>=3.10
25
- Requires-Dist: aiolimiter>=1.2.0
26
- Requires-Dist: httpx[http2]>=0.28.0
27
- Requires-Dist: pydantic>=2.10.3
28
- Requires-Dist: rich>=13.9.4
29
- Requires-Dist: tenacity>=9.0.0
30
- Requires-Dist: typer>=0.15.0
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: aiolimiter>=1.2.1
27
+ Requires-Dist: dependamerge>=0.1.19
28
+ Requires-Dist: httpx[http2]>=0.28.1
29
+ Requires-Dist: pydantic>=2.12.5
30
+ Requires-Dist: rich>=14.2.0
31
+ Requires-Dist: tenacity>=9.1.2
32
+ Requires-Dist: typer>=0.20.0
31
33
  Provides-Extra: dev
32
- Requires-Dist: mypy>=1.13.0; extra == 'dev'
33
- Requires-Dist: pre-commit>=4.0.1; extra == 'dev'
34
- Requires-Dist: pytest-asyncio>=0.25.0; extra == 'dev'
35
- Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
36
- Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
37
- Requires-Dist: pytest>=8.3.4; extra == 'dev'
38
- Requires-Dist: ruff>=0.8.4; extra == 'dev'
34
+ Requires-Dist: mypy>=1.19.0; extra == 'dev'
35
+ Requires-Dist: pre-commit>=4.5.0; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
38
+ Requires-Dist: pytest-mock>=3.15.1; extra == 'dev'
39
+ Requires-Dist: pytest>=9.0.2; extra == 'dev'
40
+ Requires-Dist: ruff>=0.14.8; extra == 'dev'
39
41
  Description-Content-Type: text/markdown
40
42
 
41
43
  <!--
@@ -313,7 +315,7 @@ pull-request-fixer myorg --fix-title --blocked-only
313
315
  ```
314
316
  <!-- write-good-enable -->
315
317
 
316
- **Fix files in multiple PRs:**
318
+ **Fix files across PRs:**
317
319
 
318
320
  <!-- write-good-disable -->
319
321
  ```bash
@@ -355,6 +357,7 @@ comment.
355
357
  ## Options
356
358
 
357
359
  <!-- markdownlint-disable MD013 -->
360
+ <!-- write-good-disable -->
358
361
 
359
362
  | Flag | Short | Default | Description |
360
363
  | ------------------ | ----- | --------------- | ----------------------------------------------------- |
@@ -379,6 +382,7 @@ comment.
379
382
  | `--log-level` | | `INFO` | Set logging level |
380
383
  | `--version` | | | Show version and exit |
381
384
 
385
+ <!-- write-good-enable -->
382
386
  <!-- markdownlint-enable MD013 -->
383
387
 
384
388
  ## How It Works
@@ -521,15 +525,13 @@ pull-request-fixer https://github.com/lfreleng-actions/make-action/pull/40 \
521
525
 
522
526
  Output:
523
527
 
524
- <!-- write-good-disable -->
525
528
  ```text
526
529
  🔍 Processing PR: https://github.com/lfreleng-actions/make-action/pull/40
527
530
  🔧 Will fix: files
528
- 🏃 Dry run mode: changes will not be applied
531
+ 🏃 Dry run mode: the tool will not apply changes
529
532
 
530
533
  📝 Fixing files in PR...
531
534
  ```
532
- <!-- write-good-enable -->
533
535
 
534
536
  ✅ Would fix 1 file
535
537
  📂 action.yaml
@@ -273,7 +273,7 @@ pull-request-fixer myorg --fix-title --blocked-only
273
273
  ```
274
274
  <!-- write-good-enable -->
275
275
 
276
- **Fix files in multiple PRs:**
276
+ **Fix files across PRs:**
277
277
 
278
278
  <!-- write-good-disable -->
279
279
  ```bash
@@ -315,6 +315,7 @@ comment.
315
315
  ## Options
316
316
 
317
317
  <!-- markdownlint-disable MD013 -->
318
+ <!-- write-good-disable -->
318
319
 
319
320
  | Flag | Short | Default | Description |
320
321
  | ------------------ | ----- | --------------- | ----------------------------------------------------- |
@@ -339,6 +340,7 @@ comment.
339
340
  | `--log-level` | | `INFO` | Set logging level |
340
341
  | `--version` | | | Show version and exit |
341
342
 
343
+ <!-- write-good-enable -->
342
344
  <!-- markdownlint-enable MD013 -->
343
345
 
344
346
  ## How It Works
@@ -481,15 +483,13 @@ pull-request-fixer https://github.com/lfreleng-actions/make-action/pull/40 \
481
483
 
482
484
  Output:
483
485
 
484
- <!-- write-good-disable -->
485
486
  ```text
486
487
  🔍 Processing PR: https://github.com/lfreleng-actions/make-action/pull/40
487
488
  🔧 Will fix: files
488
- 🏃 Dry run mode: changes will not be applied
489
+ 🏃 Dry run mode: the tool will not apply changes
489
490
 
490
491
  📝 Fixing files in PR...
491
492
  ```
492
- <!-- write-good-enable -->
493
493
 
494
494
  ✅ Would fix 1 file
495
495
  📂 action.yaml
@@ -10,7 +10,7 @@ name = "pull-request-fixer"
10
10
  dynamic = ["version"]
11
11
  description = "Pull request formatter and fixer with GitHub integration"
12
12
  readme = "README.md"
13
- requires-python = ">=3.10,<3.14"
13
+ requires-python = ">=3.10"
14
14
  license = {text = "Apache-2.0"}
15
15
  authors = [
16
16
  {name = "The Linux Foundation", email = "releng@linuxfoundation.org"},
@@ -27,27 +27,29 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3.11",
28
28
  "Programming Language :: Python :: 3.12",
29
29
  "Programming Language :: Python :: 3.13",
30
+ "Programming Language :: Python :: 3.14",
30
31
  "Topic :: Software Development :: Quality Assurance",
31
32
  "Topic :: Text Processing :: Markup :: Markdown",
32
33
  ]
33
34
  dependencies = [
34
- "typer>=0.15.0",
35
- "rich>=13.9.4",
36
- "httpx[http2]>=0.28.0",
37
- "pydantic>=2.10.3",
38
- "aiolimiter>=1.2.0",
39
- "tenacity>=9.0.0",
35
+ "typer>=0.20.0",
36
+ "rich>=14.2.0",
37
+ "httpx[http2]>=0.28.1",
38
+ "pydantic>=2.12.5",
39
+ "aiolimiter>=1.2.1",
40
+ "tenacity>=9.1.2",
41
+ "dependamerge>=0.1.19",
40
42
  ]
41
43
 
42
44
  [project.optional-dependencies]
43
45
  dev = [
44
- "pytest>=8.3.4",
45
- "pytest-cov>=6.0.0",
46
- "pytest-asyncio>=0.25.0",
47
- "pytest-mock>=3.14.0",
48
- "mypy>=1.13.0",
49
- "ruff>=0.8.4",
50
- "pre-commit>=4.0.1",
46
+ "pytest>=9.0.2",
47
+ "pytest-cov>=7.0.0",
48
+ "pytest-asyncio>=1.3.0",
49
+ "pytest-mock>=3.15.1",
50
+ "mypy>=1.19.0",
51
+ "ruff>=0.14.8",
52
+ "pre-commit>=4.5.0",
51
53
  ]
52
54
 
53
55
  [project.urls]
@@ -60,6 +62,7 @@ pull-request-fixer = "pull_request_fixer.cli:cli"
60
62
 
61
63
  [tool.hatch.version]
62
64
  source = "vcs"
65
+ fallback-version = "0.0.0"
63
66
 
64
67
  [tool.hatch.build.hooks.vcs]
65
68
  version-file = "src/pull_request_fixer/_version.py"
@@ -71,7 +74,7 @@ packages = ["src/pull_request_fixer"]
71
74
  include = ["src", "README.md", "LICENSE*", "pyproject.toml", ".pre-commit-hooks.yaml"]
72
75
 
73
76
  [tool.ruff]
74
- target-version = "py310"
77
+ target-version = "py312"
75
78
  line-length = 80
76
79
  exclude = [
77
80
  "src/pull_request_fixer/_version.py",
@@ -114,8 +117,81 @@ indent-style = "space"
114
117
  skip-magic-trailing-comma = false
115
118
  line-ending = "auto"
116
119
 
120
+ [tool.basedpyright]
121
+ pythonVersion = "3.12"
122
+ pythonPlatform = "All"
123
+ typeCheckingMode = "standard"
124
+ include = ["src", "tests", "scripts", "test_blocked_pr_logic.py", "test_integration.py"]
125
+ exclude = [
126
+ "src/pull_request_fixer/_version.py",
127
+ ".venv",
128
+ "build",
129
+ "dist",
130
+ ]
131
+
132
+ # --- Errors we want to catch and fix ---
133
+ reportMissingTypeArgument = "warning"
134
+ reportOptionalMemberAccess = "warning"
135
+ reportCallIssue = "warning"
136
+ reportAssignmentType = "warning"
137
+ reportReturnType = "warning"
138
+ reportGeneralTypeIssues = "warning"
139
+ reportUninitializedInstanceVariable = "warning"
140
+
141
+ # --- Intentional patterns we suppress ---
142
+ # Third-party imports cannot be resolved in pre-commit.ci sandbox
143
+ reportMissingImports = false
144
+ # Typer uses Option() calls in parameter defaults by design
145
+ reportCallInDefaultInitializer = false
146
+ # Typer parameters are consumed by the framework, not the function body
147
+ reportUnusedParameter = false
148
+ # Test files legitimately access private members for testing
149
+ reportPrivateUsage = false
150
+ # Test files and CLI create values that are intentionally unused
151
+ reportUnusedCallResult = false
152
+ # Many third-party and dynamic types produce Any
153
+ reportAny = false
154
+ reportExplicitAny = false
155
+ reportUnknownParameterType = false
156
+ reportUnknownMemberType = false
157
+ reportUnknownVariableType = false
158
+ reportUnknownLambdaType = false
159
+ reportUnknownArgumentType = false
160
+ # Missing type annotations are tracked separately (mypy)
161
+ reportMissingParameterType = false
162
+ # String concatenation in GraphQL queries and Rich output is intentional
163
+ reportImplicitStringConcatenation = false
164
+ # Class attributes in tests don't need annotations
165
+ reportUnannotatedClassAttribute = false
166
+ # Unused imports are handled by ruff (F401)
167
+ reportUnusedImport = false
168
+ # Unused functions are typically CLI helpers or test utilities
169
+ reportUnusedFunction = false
170
+ # Unused variables are handled by ruff (F841)
171
+ reportUnusedVariable = false
172
+ # Deprecated typing imports in auto-generated files
173
+ reportDeprecated = false
174
+ # Override decorators are not required
175
+ reportImplicitOverride = false
176
+ # isinstance checks used for runtime safety
177
+ reportUnnecessaryIsInstance = false
178
+ # Comparison checks used for runtime safety
179
+ reportUnnecessaryComparison = false
180
+ # Unreachable code used for defensive programming
181
+ reportUnreachable = false
182
+ # Missing type stubs for third-party packages
183
+ reportMissingTypeStubs = false
184
+ # Constant redefinition patterns (e.g. try/except fallback classes)
185
+ reportConstantRedefinition = false
186
+ # Attribute access issues in tests (mock objects)
187
+ reportAttributeAccessIssue = false
188
+ # Operator issues in tests (mock assertions)
189
+ reportOperatorIssue = false
190
+ # Index issues in tests (mock return values)
191
+ reportIndexIssue = false
192
+
117
193
  [tool.mypy]
118
- python_version = "3.10"
194
+ python_version = "3.12"
119
195
  strict = true
120
196
  warn_return_any = true
121
197
  warn_unused_configs = true
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.2'
22
+ __version_tuple__ = version_tuple = (0, 1, 2)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,216 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: 2025 The Linux Foundation
3
+
4
+ """
5
+ Blocked PR scanner using dependamerge's GitHubService.
6
+
7
+ This module wraps dependamerge's PR scanning functionality to provide
8
+ consistent blocked PR detection across both tools.
9
+ """
10
+
11
+ import asyncio
12
+ from collections.abc import AsyncIterator
13
+ import logging
14
+ from typing import Any
15
+
16
+ from dependamerge.github_service import GitHubService
17
+ from dependamerge.models import UnmergeablePR
18
+
19
+ from .progress_tracker import ProgressTracker
20
+
21
+
22
+ class BlockedPRScanner:
23
+ """Scanner for blocked/unmergeable pull requests using dependamerge logic."""
24
+
25
+ def __init__(
26
+ self,
27
+ token: str,
28
+ progress_tracker: ProgressTracker | None = None,
29
+ max_repo_tasks: int = 10,
30
+ ):
31
+ """Initialize the blocked PR scanner.
32
+
33
+ Args:
34
+ token: GitHub authentication token
35
+ progress_tracker: Optional progress tracker for UI updates
36
+ max_repo_tasks: Maximum number of repositories to process concurrently
37
+ """
38
+ self.token = token
39
+ self.progress_tracker = progress_tracker
40
+ self.max_repo_tasks = max_repo_tasks
41
+ self.logger = logging.getLogger(__name__)
42
+
43
+ # Initialize the dependamerge GitHubService
44
+ self.service = GitHubService(
45
+ token=token,
46
+ progress_tracker=progress_tracker,
47
+ max_repo_tasks=max_repo_tasks,
48
+ )
49
+
50
+ async def close(self) -> None:
51
+ """Close the underlying HTTP client."""
52
+ await self.service.close()
53
+
54
+ async def scan_organization_for_blocked_prs(
55
+ self,
56
+ organization: str,
57
+ include_drafts: bool = False,
58
+ ) -> AsyncIterator[tuple[str, str, dict[str, Any], UnmergeablePR]]:
59
+ """Scan an organization for blocked/unmergeable pull requests.
60
+
61
+ This method uses dependamerge's GitHubService to analyze PRs and only
62
+ yields those that are truly blocked (have merge conflicts, failing checks,
63
+ behind base, etc.). This ensures consistency with dependamerge's reporting.
64
+
65
+ Args:
66
+ organization: Organization name to scan
67
+ include_drafts: Whether to include draft PRs (default False)
68
+
69
+ Yields:
70
+ Tuples of (owner, repo_name, pr_data, unmergeable_pr) where:
71
+ - owner: Repository owner
72
+ - repo_name: Repository name
73
+ - pr_data: Raw PR data dict from GraphQL
74
+ - unmergeable_pr: UnmergeablePR model with blocking reasons
75
+ """
76
+ self.logger.debug(
77
+ f"Starting blocked PR scan of organization: {organization}"
78
+ )
79
+
80
+ # Start progress tracker if available
81
+ if self.progress_tracker:
82
+ self.progress_tracker.start()
83
+
84
+ try:
85
+ # Use dependamerge's scan_organization which already:
86
+ # 1. Counts repositories
87
+ # 2. Paginates through PRs
88
+ # 3. Analyzes blocking conditions
89
+ # 4. Tracks progress correctly
90
+ scan_result = await self.service.scan_organization(
91
+ organization, include_drafts=include_drafts
92
+ )
93
+ finally:
94
+ # Stop progress tracker before yielding results
95
+ if self.progress_tracker:
96
+ self.progress_tracker.stop()
97
+
98
+ # Display summary
99
+ if self.progress_tracker:
100
+ from rich.console import Console
101
+
102
+ console = Console()
103
+ console.print(
104
+ f"\n✅ Scan completed\n"
105
+ f"📊 Found {len(scan_result.unmergeable_prs)} blocked PRs "
106
+ f"out of {scan_result.total_prs} total PRs across {scan_result.scanned_repositories} repositories\n"
107
+ )
108
+ else:
109
+ self.logger.info(
110
+ f"Scan complete: found {len(scan_result.unmergeable_prs)} blocked PRs "
111
+ f"out of {scan_result.total_prs} total PRs across {scan_result.scanned_repositories} repositories"
112
+ )
113
+
114
+ # Now we need to fetch the full PR data for each unmergeable PR
115
+ # to provide it in the same format as the old scanner
116
+ for unmergeable_pr in scan_result.unmergeable_prs:
117
+ # Split repository name
118
+ parts = unmergeable_pr.repository.split("/", 1)
119
+ if len(parts) != 2:
120
+ self.logger.warning(
121
+ f"Invalid repository format: {unmergeable_pr.repository}"
122
+ )
123
+ continue
124
+
125
+ owner, repo_name = parts
126
+
127
+ # Create a minimal pr_data dict that matches what the old scanner returned
128
+ # This is primarily for compatibility with existing code
129
+ pr_data = {
130
+ "number": unmergeable_pr.pr_number,
131
+ "title": unmergeable_pr.title,
132
+ "author": {"login": unmergeable_pr.author},
133
+ "url": unmergeable_pr.url,
134
+ "createdAt": unmergeable_pr.created_at,
135
+ "updatedAt": unmergeable_pr.updated_at,
136
+ # Include blocking information for backward compatibility
137
+ "_unmergeable_reasons": [
138
+ {
139
+ "type": reason.type,
140
+ "description": reason.description,
141
+ "details": reason.details,
142
+ }
143
+ for reason in unmergeable_pr.reasons
144
+ ],
145
+ }
146
+
147
+ yield owner, repo_name, pr_data, unmergeable_pr
148
+
149
+ async def scan_organization_all_prs(
150
+ self,
151
+ organization: str,
152
+ include_drafts: bool = False,
153
+ ) -> AsyncIterator[tuple[str, str, dict[str, Any], bool]]:
154
+ """Scan an organization for ALL pull requests, marking which are blocked.
155
+
156
+ This is useful when you want to process all PRs but need to know which
157
+ ones are blocked.
158
+
159
+ Args:
160
+ organization: Organization name to scan
161
+ include_drafts: Whether to include draft PRs
162
+
163
+ Yields:
164
+ Tuples of (owner, repo_name, pr_data, is_blocked) where:
165
+ - owner: Repository owner
166
+ - repo_name: Repository name
167
+ - pr_data: Raw PR data dict from GraphQL
168
+ - is_blocked: Boolean indicating if PR is blocked
169
+ """
170
+ # For this use case, we need to fetch ALL PRs, not just blocked ones
171
+ # This requires using the lower-level iteration methods
172
+ # For now, we'll note this is a TODO and focus on the blocked-only case
173
+ raise NotImplementedError(
174
+ "Scanning all PRs (not just blocked) is not yet implemented. "
175
+ "Use scan_organization_for_blocked_prs() for blocked-only scanning."
176
+ )
177
+
178
+ def is_pr_blocked(self, pr: dict[str, Any]) -> tuple[bool, list[str]]:
179
+ """Check if a PR is blocked from merging (synchronous wrapper).
180
+
181
+ This method provides backward compatibility with the old scanner interface.
182
+ It uses the same logic as dependamerge's _analyze_pr_node.
183
+
184
+ Args:
185
+ pr: PR data from GraphQL
186
+
187
+ Returns:
188
+ Tuple of (is_blocked, list of blocking reasons as strings)
189
+ """
190
+ # Use asyncio to call the async version
191
+ return asyncio.run(self._is_pr_blocked_async(pr))
192
+
193
+ async def _is_pr_blocked_async(
194
+ self, pr: dict[str, Any]
195
+ ) -> tuple[bool, list[str]]:
196
+ """Async version of is_pr_blocked.
197
+
198
+ Args:
199
+ pr: PR data from GraphQL
200
+
201
+ Returns:
202
+ Tuple of (is_blocked, list of blocking reasons as strings)
203
+ """
204
+ # Use dependamerge's _analyze_pr_node to check if PR is blocked
205
+ result = await self.service._analyze_pr_node(
206
+ repo_full_name="temp/repo", # Not used for just checking blocked status
207
+ pr=pr,
208
+ include_drafts=False,
209
+ )
210
+
211
+ if result is None:
212
+ return False, []
213
+
214
+ # Extract reason descriptions
215
+ reasons = [reason.description for reason in result.reasons]
216
+ return True, reasons