napistu 0.2.5.dev7__py3-none-any.whl → 0.3.1__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 (107) hide show
  1. napistu/__main__.py +126 -96
  2. napistu/constants.py +35 -41
  3. napistu/context/__init__.py +10 -0
  4. napistu/context/discretize.py +462 -0
  5. napistu/context/filtering.py +387 -0
  6. napistu/gcs/__init__.py +1 -1
  7. napistu/identifiers.py +74 -15
  8. napistu/indices.py +68 -0
  9. napistu/ingestion/__init__.py +1 -1
  10. napistu/ingestion/bigg.py +47 -62
  11. napistu/ingestion/constants.py +18 -133
  12. napistu/ingestion/gtex.py +113 -0
  13. napistu/ingestion/hpa.py +147 -0
  14. napistu/ingestion/sbml.py +0 -97
  15. napistu/ingestion/string.py +2 -2
  16. napistu/matching/__init__.py +10 -0
  17. napistu/matching/constants.py +18 -0
  18. napistu/matching/interactions.py +518 -0
  19. napistu/matching/mount.py +529 -0
  20. napistu/matching/species.py +510 -0
  21. napistu/mcp/__init__.py +7 -4
  22. napistu/mcp/__main__.py +128 -72
  23. napistu/mcp/client.py +16 -25
  24. napistu/mcp/codebase.py +201 -145
  25. napistu/mcp/component_base.py +170 -0
  26. napistu/mcp/config.py +223 -0
  27. napistu/mcp/constants.py +45 -2
  28. napistu/mcp/documentation.py +253 -136
  29. napistu/mcp/documentation_utils.py +13 -48
  30. napistu/mcp/execution.py +372 -305
  31. napistu/mcp/health.py +47 -65
  32. napistu/mcp/profiles.py +10 -6
  33. napistu/mcp/server.py +161 -80
  34. napistu/mcp/tutorials.py +139 -87
  35. napistu/modify/__init__.py +1 -1
  36. napistu/modify/gaps.py +1 -1
  37. napistu/network/__init__.py +1 -1
  38. napistu/network/constants.py +101 -34
  39. napistu/network/data_handling.py +388 -0
  40. napistu/network/ig_utils.py +351 -0
  41. napistu/network/napistu_graph_core.py +354 -0
  42. napistu/network/neighborhoods.py +40 -40
  43. napistu/network/net_create.py +373 -309
  44. napistu/network/net_propagation.py +47 -19
  45. napistu/network/{net_utils.py → ng_utils.py} +124 -272
  46. napistu/network/paths.py +67 -51
  47. napistu/network/precompute.py +11 -11
  48. napistu/ontologies/__init__.py +10 -0
  49. napistu/ontologies/constants.py +129 -0
  50. napistu/ontologies/dogma.py +243 -0
  51. napistu/ontologies/genodexito.py +649 -0
  52. napistu/ontologies/mygene.py +369 -0
  53. napistu/ontologies/renaming.py +198 -0
  54. napistu/rpy2/__init__.py +229 -86
  55. napistu/rpy2/callr.py +47 -77
  56. napistu/rpy2/constants.py +24 -23
  57. napistu/rpy2/rids.py +61 -648
  58. napistu/sbml_dfs_core.py +587 -222
  59. napistu/scverse/__init__.py +15 -0
  60. napistu/scverse/constants.py +28 -0
  61. napistu/scverse/loading.py +727 -0
  62. napistu/utils.py +118 -10
  63. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/METADATA +8 -3
  64. napistu-0.3.1.dist-info/RECORD +133 -0
  65. tests/conftest.py +22 -0
  66. tests/test_context_discretize.py +56 -0
  67. tests/test_context_filtering.py +267 -0
  68. tests/test_identifiers.py +100 -0
  69. tests/test_indices.py +65 -0
  70. tests/{test_edgelist.py → test_ingestion_napistu_edgelist.py} +2 -2
  71. tests/test_matching_interactions.py +108 -0
  72. tests/test_matching_mount.py +305 -0
  73. tests/test_matching_species.py +394 -0
  74. tests/test_mcp_config.py +193 -0
  75. tests/test_mcp_documentation_utils.py +12 -3
  76. tests/test_mcp_server.py +156 -19
  77. tests/test_network_data_handling.py +397 -0
  78. tests/test_network_ig_utils.py +23 -0
  79. tests/test_network_neighborhoods.py +19 -0
  80. tests/test_network_net_create.py +459 -0
  81. tests/test_network_ng_utils.py +30 -0
  82. tests/test_network_paths.py +56 -0
  83. tests/{test_precomputed_distances.py → test_network_precompute.py} +8 -6
  84. tests/test_ontologies_genodexito.py +58 -0
  85. tests/test_ontologies_mygene.py +39 -0
  86. tests/test_ontologies_renaming.py +110 -0
  87. tests/test_rpy2_callr.py +79 -0
  88. tests/test_rpy2_init.py +151 -0
  89. tests/test_sbml.py +0 -31
  90. tests/test_sbml_dfs_core.py +134 -10
  91. tests/test_scverse_loading.py +778 -0
  92. tests/test_set_coverage.py +2 -2
  93. tests/test_utils.py +121 -1
  94. napistu/mechanism_matching.py +0 -1353
  95. napistu/rpy2/netcontextr.py +0 -467
  96. napistu-0.2.5.dev7.dist-info/RECORD +0 -98
  97. tests/test_igraph.py +0 -367
  98. tests/test_mechanism_matching.py +0 -784
  99. tests/test_net_utils.py +0 -149
  100. tests/test_netcontextr.py +0 -105
  101. tests/test_rpy2.py +0 -61
  102. /napistu/ingestion/{cpr_edgelist.py → napistu_edgelist.py} +0 -0
  103. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/WHEEL +0 -0
  104. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/entry_points.txt +0 -0
  105. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/licenses/LICENSE +0 -0
  106. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/top_level.txt +0 -0
  107. /tests/{test_obo.py → test_ingestion_obo.py} +0 -0
@@ -2,165 +2,282 @@
2
2
  Documentation components for the Napistu MCP server.
3
3
  """
4
4
 
5
+ from typing import Dict, Any
6
+ import logging
7
+
5
8
  from fastmcp import FastMCP
6
9
 
10
+ from napistu.mcp.component_base import ComponentState, MCPComponent
7
11
  from napistu.mcp import documentation_utils
8
12
  from napistu.mcp import utils as mcp_utils
9
- from napistu.mcp.constants import DOCUMENTATION, READMES, REPOS_WITH_ISSUES
10
-
11
- # Global cache for documentation content
12
- _docs_cache = {
13
- DOCUMENTATION.README: {},
14
- DOCUMENTATION.WIKI: {},
15
- DOCUMENTATION.ISSUES: {},
16
- DOCUMENTATION.PRS: {},
17
- DOCUMENTATION.PACKAGEDOWN: {},
18
- }
13
+ from napistu.mcp.constants import DOCUMENTATION, READMES, REPOS_WITH_ISSUES, WIKI_PAGES
19
14
 
15
+ logger = logging.getLogger(__name__)
20
16
 
21
- async def initialize_components() -> bool:
22
- """
23
- Initialize documentation components.
24
- Loads documentation content and should be called before the server starts handling requests.
25
-
26
- Returns
27
- -------
28
- bool
29
- True if initialization is successful.
30
- """
31
- global _docs_cache
32
- # Load documentation from the READMES dict
33
- for name, url in READMES.items():
34
- _docs_cache[DOCUMENTATION.README][name] = (
35
- await documentation_utils.load_readme_content(url)
36
- )
37
- # Load issue and PR summaries with the GitHub API
38
- for repo in REPOS_WITH_ISSUES:
39
- _docs_cache[DOCUMENTATION.ISSUES][repo] = await documentation_utils.list_issues(
40
- repo
41
- )
42
- _docs_cache[DOCUMENTATION.PRS][repo] = (
43
- await documentation_utils.list_pull_requests(repo)
44
- )
45
- return True
46
17
 
18
+ class DocumentationState(ComponentState):
19
+ """State management for documentation component."""
47
20
 
48
- def register_components(mcp: FastMCP):
49
- """
50
- Register documentation components with the MCP server.
21
+ def __init__(self):
22
+ super().__init__()
23
+ self.docs_cache: Dict[str, Dict[str, Any]] = {
24
+ DOCUMENTATION.README: {},
25
+ DOCUMENTATION.WIKI: {},
26
+ DOCUMENTATION.ISSUES: {},
27
+ DOCUMENTATION.PRS: {},
28
+ DOCUMENTATION.PACKAGEDOWN: {},
29
+ }
51
30
 
52
- Args:
53
- mcp: FastMCP server instance
54
- """
31
+ def is_healthy(self) -> bool:
32
+ """Component is healthy if it has loaded any documentation."""
33
+ return any(bool(section) for section in self.docs_cache.values())
55
34
 
56
- # Register resources
57
- @mcp.resource("napistu://documentation/summary")
58
- async def get_documentation_summary():
59
- """
60
- Get a summary of all available documentation.
61
- """
35
+ def get_health_details(self) -> Dict[str, Any]:
36
+ """Provide documentation-specific health details."""
62
37
  return {
63
- "readme_files": list(_docs_cache[DOCUMENTATION.README].keys()),
64
- "issues": list(_docs_cache[DOCUMENTATION.ISSUES].keys()),
65
- "prs": list(_docs_cache[DOCUMENTATION.PRS].keys()),
66
- "wiki_pages": list(_docs_cache[DOCUMENTATION.WIKI].keys()),
67
- "packagedown_sections": list(_docs_cache[DOCUMENTATION.PACKAGEDOWN].keys()),
38
+ "readme_count": len(self.docs_cache[DOCUMENTATION.README]),
39
+ "wiki_pages": len(self.docs_cache[DOCUMENTATION.WIKI]),
40
+ "issues_repos": len(self.docs_cache[DOCUMENTATION.ISSUES]),
41
+ "prs_repos": len(self.docs_cache[DOCUMENTATION.PRS]),
42
+ "total_sections": sum(len(section) for section in self.docs_cache.values()),
68
43
  }
69
44
 
70
- @mcp.resource("napistu://documentation/readme/{file_name}")
71
- async def get_readme_content(file_name: str):
72
- """
73
- Get the content of a specific README file.
74
45
 
75
- Args:
76
- file_name: Name of the README file
77
- """
78
- if file_name not in _docs_cache[DOCUMENTATION.README]:
79
- return {"error": f"README file {file_name} not found"}
46
+ class DocumentationComponent(MCPComponent):
47
+ """MCP component for documentation management and search."""
80
48
 
81
- return {
82
- "content": _docs_cache[DOCUMENTATION.README][file_name],
83
- "format": "markdown",
84
- }
49
+ def _create_state(self) -> DocumentationState:
50
+ """Create documentation-specific state."""
51
+ return DocumentationState()
85
52
 
86
- @mcp.resource("napistu://documentation/issues/{repo}")
87
- async def get_issues(repo: str):
88
- """
89
- Get the list of issues for a given repository.
53
+ async def initialize(self) -> bool:
90
54
  """
91
- return _docs_cache[DOCUMENTATION.ISSUES].get(repo, [])
55
+ Initialize documentation component by loading all documentation sources.
92
56
 
93
- @mcp.resource("napistu://documentation/prs/{repo}")
94
- async def get_prs(repo: str):
95
- """
96
- Get the list of pull requests for a given repository.
57
+ Returns
58
+ -------
59
+ bool
60
+ True if at least some documentation was loaded successfully
97
61
  """
98
- return _docs_cache[DOCUMENTATION.PRS].get(repo, [])
62
+ success_count = 0
63
+ total_operations = 0
99
64
 
100
- @mcp.resource("napistu://documentation/issue/{repo}/{number}")
101
- async def get_issue_resource(repo: str, number: int):
102
- """
103
- Get a single issue by number for a given repository.
104
- """
105
- # Try cache first
106
- cached = next(
107
- (
108
- i
109
- for i in _docs_cache[DOCUMENTATION.ISSUES].get(repo, [])
110
- if i["number"] == number
111
- ),
112
- None,
65
+ # Load README files
66
+ logger.info("Loading README files...")
67
+ for name, url in READMES.items():
68
+ total_operations += 1
69
+ try:
70
+ content = await documentation_utils.load_readme_content(url)
71
+ self.state.docs_cache[DOCUMENTATION.README][name] = content
72
+ success_count += 1
73
+ logger.debug(f"Loaded README: {name}")
74
+ except Exception as e:
75
+ logger.warning(f"Failed to load README {name}: {e}")
76
+
77
+ # Load wiki pages
78
+ logger.info("Loading wiki pages...")
79
+ for page in WIKI_PAGES:
80
+ total_operations += 1
81
+ try:
82
+ content = await documentation_utils.fetch_wiki_page(page)
83
+ self.state.docs_cache[DOCUMENTATION.WIKI][page] = content
84
+ success_count += 1
85
+ logger.debug(f"Loaded wiki page: {page}")
86
+ except Exception as e:
87
+ logger.warning(f"Failed to load wiki page {page}: {e}")
88
+
89
+ # Load issues and PRs
90
+ logger.info("Loading issues and pull requests...")
91
+ for repo in REPOS_WITH_ISSUES:
92
+ total_operations += 2 # Issues and PRs
93
+ try:
94
+ issues = await documentation_utils.list_issues(repo)
95
+ self.state.docs_cache[DOCUMENTATION.ISSUES][repo] = issues
96
+ success_count += 1
97
+ logger.debug(f"Loaded issues for repo: {repo}")
98
+ except Exception as e:
99
+ logger.warning(f"Failed to load issues for {repo}: {e}")
100
+
101
+ try:
102
+ prs = await documentation_utils.list_pull_requests(repo)
103
+ self.state.docs_cache[DOCUMENTATION.PRS][repo] = prs
104
+ success_count += 1
105
+ logger.debug(f"Loaded PRs for repo: {repo}")
106
+ except Exception as e:
107
+ logger.warning(f"Failed to load PRs for {repo}: {e}")
108
+
109
+ logger.info(
110
+ f"Documentation loading complete: {success_count}/{total_operations} operations successful"
113
111
  )
114
- if cached:
115
- return cached
116
- # Fallback to live fetch
117
- return await documentation_utils.get_issue(repo, number)
118
112
 
119
- @mcp.resource("napistu://documentation/pr/{repo}/{number}")
120
- async def get_pr_resource(repo: str, number: int):
121
- """
122
- Get a single pull request by number for a given repository.
113
+ # Consider successful if at least some documentation loaded
114
+ return success_count > 0
115
+
116
+ def register(self, mcp: FastMCP) -> None:
123
117
  """
124
- # Try cache first
125
- cached = next(
126
- (
127
- pr
128
- for pr in _docs_cache[DOCUMENTATION.PRS].get(repo, [])
129
- if pr["number"] == number
130
- ),
131
- None,
132
- )
133
- if cached:
134
- return cached
135
- # Fallback to live fetch
136
- return await documentation_utils.get_issue(repo, number)
137
-
138
- # Register tools
139
- @mcp.tool()
140
- async def search_documentation(query: str):
118
+ Register documentation resources and tools with the MCP server.
119
+
120
+ Parameters
121
+ ----------
122
+ mcp : FastMCP
123
+ FastMCP server instance
141
124
  """
142
- Search all documentation for a specific query.
143
125
 
144
- Args:
145
- query: Search term
126
+ # Register resources
127
+ @mcp.resource("napistu://documentation/summary")
128
+ async def get_documentation_summary():
129
+ """Get a summary of all available documentation."""
130
+ return {
131
+ "readme_files": list(
132
+ self.state.docs_cache[DOCUMENTATION.README].keys()
133
+ ),
134
+ "issues": list(self.state.docs_cache[DOCUMENTATION.ISSUES].keys()),
135
+ "prs": list(self.state.docs_cache[DOCUMENTATION.PRS].keys()),
136
+ "wiki_pages": list(self.state.docs_cache[DOCUMENTATION.WIKI].keys()),
137
+ "packagedown_sections": list(
138
+ self.state.docs_cache[DOCUMENTATION.PACKAGEDOWN].keys()
139
+ ),
140
+ }
146
141
 
147
- Returns:
148
- Dictionary with search results organized by documentation type
149
- """
150
- results = {
151
- DOCUMENTATION.README: [],
152
- DOCUMENTATION.WIKI: [],
153
- DOCUMENTATION.ISSUES: [],
154
- DOCUMENTATION.PRS: [],
155
- DOCUMENTATION.PACKAGEDOWN: [],
156
- }
157
- # Simple text search
158
- for readme_name, content in _docs_cache[DOCUMENTATION.README].items():
159
- if query.lower() in content.lower():
160
- results[DOCUMENTATION.README].append(
161
- {
162
- "name": readme_name,
163
- "snippet": mcp_utils.get_snippet(content, query),
164
- }
165
- )
166
- return results
142
+ @mcp.resource("napistu://documentation/readme/{file_name}")
143
+ async def get_readme_content(file_name: str):
144
+ """Get the content of a specific README file."""
145
+ if file_name not in self.state.docs_cache[DOCUMENTATION.README]:
146
+ return {"error": f"README file {file_name} not found"}
147
+
148
+ return {
149
+ "content": self.state.docs_cache[DOCUMENTATION.README][file_name],
150
+ "format": "markdown",
151
+ }
152
+
153
+ @mcp.resource("napistu://documentation/issues/{repo}")
154
+ async def get_issues(repo: str):
155
+ """Get the list of issues for a given repository."""
156
+ return self.state.docs_cache[DOCUMENTATION.ISSUES].get(repo, [])
157
+
158
+ @mcp.resource("napistu://documentation/prs/{repo}")
159
+ async def get_prs(repo: str):
160
+ """Get the list of pull requests for a given repository."""
161
+ return self.state.docs_cache[DOCUMENTATION.PRS].get(repo, [])
162
+
163
+ @mcp.resource("napistu://documentation/issue/{repo}/{number}")
164
+ async def get_issue_resource(repo: str, number: int):
165
+ """Get a single issue by number for a given repository."""
166
+ # Try cache first
167
+ cached = next(
168
+ (
169
+ i
170
+ for i in self.state.docs_cache[DOCUMENTATION.ISSUES].get(repo, [])
171
+ if i["number"] == number
172
+ ),
173
+ None,
174
+ )
175
+ if cached:
176
+ return cached
177
+ # Fallback to live fetch
178
+ return await documentation_utils.get_issue(repo, number)
179
+
180
+ @mcp.resource("napistu://documentation/pr/{repo}/{number}")
181
+ async def get_pr_resource(repo: str, number: int):
182
+ """Get a single pull request by number for a given repository."""
183
+ # Try cache first
184
+ cached = next(
185
+ (
186
+ pr
187
+ for pr in self.state.docs_cache[DOCUMENTATION.PRS].get(repo, [])
188
+ if pr["number"] == number
189
+ ),
190
+ None,
191
+ )
192
+ if cached:
193
+ return cached
194
+ # Fallback to live fetch
195
+ return await documentation_utils.get_issue(repo, number)
196
+
197
+ # Register tools
198
+ @mcp.tool()
199
+ async def search_documentation(query: str):
200
+ """
201
+ Search all documentation for a specific query.
202
+
203
+ Args:
204
+ query: Search term
205
+
206
+ Returns:
207
+ Dictionary with search results organized by documentation type
208
+ """
209
+ results = {
210
+ DOCUMENTATION.README: [],
211
+ DOCUMENTATION.WIKI: [],
212
+ DOCUMENTATION.ISSUES: [],
213
+ DOCUMENTATION.PRS: [],
214
+ DOCUMENTATION.PACKAGEDOWN: [],
215
+ }
216
+
217
+ # Search README files
218
+ for readme_name, content in self.state.docs_cache[
219
+ DOCUMENTATION.README
220
+ ].items():
221
+ if query.lower() in content.lower():
222
+ results[DOCUMENTATION.README].append(
223
+ {
224
+ "name": readme_name,
225
+ "snippet": mcp_utils.get_snippet(content, query),
226
+ }
227
+ )
228
+
229
+ # Search wiki pages
230
+ for page_name, content in self.state.docs_cache[DOCUMENTATION.WIKI].items():
231
+ if query.lower() in content.lower():
232
+ results[DOCUMENTATION.WIKI].append(
233
+ {
234
+ "name": page_name,
235
+ "snippet": mcp_utils.get_snippet(content, query),
236
+ }
237
+ )
238
+
239
+ # Search issues
240
+ for repo, issues in self.state.docs_cache[DOCUMENTATION.ISSUES].items():
241
+ for issue in issues:
242
+ issue_text = f"{issue.get('title', '')} {issue.get('body', '')}"
243
+ if query.lower() in issue_text.lower():
244
+ results[DOCUMENTATION.ISSUES].append(
245
+ {
246
+ "name": f"{repo}#{issue.get('number')}",
247
+ "title": issue.get("title"),
248
+ "url": issue.get("url"),
249
+ "snippet": mcp_utils.get_snippet(issue_text, query),
250
+ }
251
+ )
252
+
253
+ # Search PRs
254
+ for repo, prs in self.state.docs_cache[DOCUMENTATION.PRS].items():
255
+ for pr in prs:
256
+ pr_text = f"{pr.get('title', '')} {pr.get('body', '')}"
257
+ if query.lower() in pr_text.lower():
258
+ results[DOCUMENTATION.PRS].append(
259
+ {
260
+ "name": f"{repo}#{pr.get('number')}",
261
+ "title": pr.get("title"),
262
+ "url": pr.get("url"),
263
+ "snippet": mcp_utils.get_snippet(pr_text, query),
264
+ }
265
+ )
266
+
267
+ return results
268
+
269
+
270
+ # Module-level component instance
271
+ _component = DocumentationComponent()
272
+
273
+
274
+ def get_component() -> DocumentationComponent:
275
+ """
276
+ Get the documentation component instance.
277
+
278
+ Returns
279
+ -------
280
+ DocumentationComponent
281
+ The documentation component instance
282
+ """
283
+ return _component
@@ -2,7 +2,6 @@
2
2
  Utilities for loading and processing documentation.
3
3
  """
4
4
 
5
- import base64
6
5
  import httpx
7
6
  import os
8
7
  from napistu.constants import PACKAGE_DEFS
@@ -25,69 +24,35 @@ async def load_readme_content(readme_url: str) -> str:
25
24
  )
26
25
 
27
26
 
28
- async def list_wiki_pages(
29
- repo: str,
30
- owner: str = PACKAGE_DEFS.GITHUB_OWNER,
31
- github_api: str = DEFAULT_GITHUB_API,
32
- ) -> list:
33
- """
34
- List all Markdown (.md) page names in a GitHub wiki using the GitHub API.
35
-
36
- Parameters
37
- ----------
38
- repo : str, optional
39
- The repository name.
40
- owner : str, optional
41
- The GitHub username or organization (default is 'napistu').
42
- github_api : str, optional
43
- The GitHub API base URL (default is 'https://api.github.com').
44
-
45
- Returns
46
- -------
47
- list of str
48
- List of page names (e.g., ['Home.md', 'Some-Page.md'])
49
- """
50
- wiki_repo = f"{repo}.wiki"
51
- url = f"{github_api}/repos/{owner}/{wiki_repo}/contents/"
52
- async with httpx.AsyncClient(headers=_get_github_headers()) as client:
53
- resp = await client.get(url)
54
- resp.raise_for_status()
55
- return [item["name"] for item in resp.json() if item["name"].endswith(".md")]
56
-
57
-
58
27
  async def fetch_wiki_page(
59
28
  page_name: str,
60
- repo: str,
29
+ repo: str = PACKAGE_DEFS.GITHUB_PROJECT_REPO,
61
30
  owner: str = PACKAGE_DEFS.GITHUB_OWNER,
62
- github_api: str = DEFAULT_GITHUB_API,
63
31
  ) -> str:
64
32
  """
65
- Fetch the decoded Markdown content of a given wiki page from a GitHub wiki.
33
+ Fetch wiki page content using raw GitHub URLs.
66
34
 
67
35
  Parameters
68
36
  ----------
69
37
  page_name : str
70
- The name of the page file (e.g., 'Home.md').
71
- repo : str, optional
72
- The repository name.
73
- owner : str, optional
74
- The GitHub username or organization (default is 'napistu').
75
- github_api : str, optional
76
- The GitHub API base URL (default is 'https://api.github.com').
38
+ The name of the page (without .md extension for wiki pages)
39
+ repo : str
40
+ The repository name
41
+ owner : str
42
+ The GitHub username or organization
77
43
 
78
44
  Returns
79
45
  -------
80
46
  str
81
- The Markdown content as a string.
47
+ The raw Markdown content
82
48
  """
83
- wiki_repo = f"{repo}.wiki"
84
- url = f"{github_api}/repos/{owner}/{wiki_repo}/contents/{page_name}"
85
- async with httpx.AsyncClient(headers=_get_github_headers()) as client:
49
+ # Use raw.githubusercontent.com for wiki pages
50
+ url = f"https://raw.githubusercontent.com/wiki/{owner}/{repo}/{page_name}.md"
51
+
52
+ async with httpx.AsyncClient() as client:
86
53
  resp = await client.get(url)
87
54
  resp.raise_for_status()
88
- data = resp.json()
89
- content = base64.b64decode(data["content"]).decode("utf-8")
90
- return content
55
+ return resp.text
91
56
 
92
57
 
93
58
  async def list_issues(