-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconf.py
More file actions
394 lines (336 loc) · 13.1 KB
/
conf.py
File metadata and controls
394 lines (336 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# -- RTD Environment Setup -------------------------------------------------
import os
import sys
sys.path.insert(0, os.path.abspath('../dev/scripts'))
# Set stable timezone and locale before any imports
os.environ['TZ'] = 'UTC'
os.environ['LC_ALL'] = 'C.UTF-8'
os.environ['LANG'] = 'C.UTF-8'
# Prevent timezone changes during initialization
os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
# Initialize timezone early and safely
try:
import time
import datetime
import locale
# Set locale to C to avoid timezone issues
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
try:
locale.setlocale(locale.LC_ALL, 'C')
except locale.Error:
pass
# Initialize time module
if hasattr(time, 'tzset'):
time.tzset()
# Pre-warm datetime
datetime.datetime.now()
except Exception:
# If anything fails, continue anyway
pass # Ignore any timezone initialization errors
# -- Project information -----------------------------------------------------
project = "PySNT"
author = "SNT contributors"
copyright = ""
# -- General configuration ---------------------------------------------------
extensions = [
"myst_nb", # Notebooks as first-class docs pages
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.intersphinx",
"sphinx.ext.linkcode", # Add source code links
"sphinx.ext.githubpages",
"sphinx.ext.mathjax", # Math rendering with MathJax
"sphinx_design",
"sphinx_copybutton", # Copy-to-clipboard buttons for code blocks
'enhanced_api_docs.sphinx_javadoc_extension'
]
# Mock imports for Read the Docs (avoid importing heavy dependencies)
import sys
sys.path.insert(0, os.path.abspath('../dev/scripts'))
from unittest.mock import MagicMock
class MockModule(MagicMock):
def __getattr__(self, name):
if name in ('__version__', 'version'):
return '1.0.0'
if name == '__name__':
return 'mock_module'
return MagicMock()
MOCK_MODULES = [
'cairosvg',
'fitz',
'install_jdk',
'jdk',
'jpype',
'matplotlib',
'matplotlib.figure',
'matplotlib.pyplot',
'matplotlib.image',
'numpy',
'pandas',
'pandasgui',
'psutil',
'pyimagej',
'pyobjc',
'pyobjc_core',
'pyobjc_framework_cocoa',
'scyjava',
'xarray',
]
for mod_name in MOCK_MODULES:
sys.modules[mod_name] = MockModule()
# Special handling for numpy version (pandas needs this)
sys.modules['numpy'].__version__ = '1.24.0'
# Autodoc configuration
autodoc_mock_imports = MOCK_MODULES
autodoc_default_options = {
'members': True,
'member-order': 'bysource',
'special-members': '__init__',
'undoc-members': True,
'exclude-members': '__weakref__',
'show-inheritance': True,
}
# More compact API documentation
autodoc_typehints = 'signature' # Show type hints in signature, not description
autodoc_typehints_format = 'short' # Use short form of type hints
autodoc_preserve_defaults = True # Show actual default values
add_module_names = False # Don't show module names in API docs
# Add the source directory to the path for autodoc
import os
sys.path.insert(0, os.path.abspath('../src'))
# Do NOT execute notebooks on RTD builds; use stored outputs instead
# To pre-execute notebooks via CI switch to "cache" and commit cached artifacts so RTD renders them instantly
nb_execution_mode = "off"
nb_execution_timeout = 30
nb_execution_allow_errors = True
nb_execution_excludepatterns = ["*"] # Exclude all notebooks from execution
# MyST configuration
myst_enable_extensions = [
"colon_fence",
"deflist",
"substitution",
"dollarmath", # Enable $...$ and $$...$$ for inline and display math
"amsmath", # Enable advanced math environments
]
# Custom MyST substitutions for FontAwesome icons
myst_substitutions = {
"fa-microscope": '<i class="fas fa-microscope"></i>',
"fa-download": '<i class="fas fa-download"></i>',
"fa-book": '<i class="fas fa-book"></i>',
"fa-rocket": '<i class="fas fa-rocket"></i>',
"fa-github": '<i class="fab fa-github"></i>',
"fa-code": '<i class="fas fa-code"></i>',
"fa-chart-bar": '<i class="fas fa-chart-bar"></i>',
}
# Copy button configuration
copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: "
copybutton_prompt_is_regexp = True
copybutton_only_copy_prompt_lines = True
copybutton_remove_prompts = True
copybutton_copy_empty_lines = False
# Intersphinx (cross-links to PyImageJ docs, etc.)
intersphinx_mapping = {
"pyimagej": ("https://py.imagej.net/en/latest/", None),
}
templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# -- Options for HTML output -------------------------------------------------
html_theme = "pydata_sphinx_theme"
# Ensure proper navigation structure
master_doc = 'index'
# Global toctree for navigation - let theme handle it automatically
html_static_path = ["_static"]
html_css_files = [
"custom.css",
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
]
html_title = "PySNT: Quantification of neuronal anatomy in Python"
html_theme_options = {
"navigation_with_keys": True,
"show_prev_next": True,
"navbar_start": ["navbar-logo"],
"navbar_center": ["navbar-nav"],
"navbar_end": ["theme-switcher", "navbar-icon-links"], # Add built-in theme switcher
"header_links_before_dropdown": 6,
"show_toc_level": 2,
"navigation_depth": 3,
"show_version_warning_banner": False,
"article_header_start": [],
"article_header_end": ["article-header-buttons.html"],
"article_footer_items": [],
"content_footer_items": [],
# Enable secondary sidebar (right TOC) for all pages except index
"secondary_sidebar_items": {
"**": ["page-toc", "edit-this-page"],
"index": [], # No TOC on index page
},
"primary_sidebar_end": [],
# External links for navbar
"external_links": [
{
"name": '<i class="fas fa-book-open"></i> Guide',
"url": "https://imagej.net/plugins/snt/",
},
{
"name": '<i class="fa-solid fa-comment"></i> Forum',
"url": "https://forum.image.sc/tag/snt",
},
{
"name": '<i class="fas fa-hands-helping"></i> Extend',
"url": "https://imagej.net/plugins/snt/contribute",
},
],
# Icon links for navbar end
"icon_links": [
{
"name": "API",
"url": "/en/latest/api.html",
"icon": "fa fa-code",
"type": "fontawesome",
},
{
"name": "GitHub",
"url": "https://github.com/morphonets/pysnt",
"icon": "fab fa-github-square",
"type": "fontawesome",
},
],
# Work in progress banner
"announcement": "🚧 Experimental project - <a href='https://github.com/morphonets/pysnt/issues' target='_blank'>feedback welcome</a>! 🚧",
# GitHub integration for edit buttons
"use_edit_page_button": True,
}
# GitHub context for edit buttons
html_context = {
"github_user": "morphonets",
"github_repo": "pysnt",
"github_version": "main",
"doc_path": "docs",
"display_github": True,
"google_site_verification": "v1xvVIrbqEYvulUApAPA3ZgLi7RtkwdppWQ00Kk_sjQ",
}
html_extra_path = ['_static/robots.txt']
# OpenSearch configuration for better search engine indexing
html_use_opensearch = 'https://pysnt.readthedocs.io'
# Add meta tags for better SEO
html_meta = {
'description': 'PySNT: Python interface to SNT (Simple Neurite Tracer) for neuronal morphology analysis and tracing',
'keywords': 'neuroscience, morphology, neuron, tracing, SNT, ImageJ, Fiji, analysis',
'author': 'SNT contributors',
'robots': 'index, follow',
'google-site-verification': 'v1xvVIrbqEYvulUApAPA3ZgLi7RtkwdppWQ00Kk_sjQ'
}
# Generate sitemap
html_baseurl = 'https://pysnt.readthedocs.io/en/latest/'
sitemap_url_scheme = "{link}"
# Images produced by notebooks
nb_render_image_options = {"align": "center"}
# -- Source code links configuration ----------------------------------------
def linkcode_resolve(domain, info):
"""
Determine the URL corresponding to Python object.
This function is called by sphinx.ext.linkcode to generate source code links.
"""
if domain != 'py':
return None
if not info['module']:
return None
# Get the module and object name
module_name = info['module']
fullname = info['fullname']
# Only link to our own modules (pysnt.*)
if not module_name.startswith('pysnt'):
return None
# GitHub repository information
github_user = "morphonets"
github_repo = "pysnt"
github_branch = "main" # or "master" depending on your default branch
try:
# Import the module to get the source file
import importlib
import inspect
# Handle submodules
try:
mod = importlib.import_module(module_name)
except ImportError:
return None
# Get the object from the module
obj = mod
for part in fullname.split('.'):
try:
obj = getattr(obj, part)
except AttributeError:
return None
# Get the source file and line number
try:
source_file = inspect.getsourcefile(obj)
source_lines = inspect.getsourcelines(obj)
line_number = source_lines[1]
except (OSError, TypeError):
return None
if source_file is None:
return None
# Convert absolute path to relative path from project root
import os
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
try:
rel_path = os.path.relpath(source_file, project_root)
except ValueError:
# Can't make relative path (different drives on Windows, etc.)
return None
# Normalize path separators for URLs
rel_path = rel_path.replace(os.sep, '/')
# Construct GitHub URL
github_url = (
f"https://github.com/{github_user}/{github_repo}/blob/{github_branch}/"
f"{rel_path}#L{line_number}"
)
return github_url
except Exception:
# If anything goes wrong, just don't provide a link
return None
# Sphinx setup hook to ensure docstring enhancements are applied
def setup(app):
"""Sphinx setup hook to apply docstring enhancements."""
def apply_enhancements_after_import(app, what, name, obj, options, lines):
"""Apply docstring enhancements after autodoc imports a module."""
# This runs after autodoc imports each object
# We need to re-apply enhancements here
if what in ('class', 'module'):
try:
import sys
import os
import importlib.util
# Load the enhancements dictionary
src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src'))
enhancements_path = os.path.join(src_path, 'pysnt', '_docstring_enhancements.py')
# Load the module to get the enhanced_docstrings dict
spec = importlib.util.spec_from_file_location("_temp_enhancements", enhancements_path)
if spec and spec.loader:
temp_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(temp_module)
# Get the class name from the full name
if what == 'class':
class_name = name.split('.')[-1]
# Check if we have an enhancement for this class
if hasattr(temp_module, 'enhance_class_docstrings'):
# Get the enhanced_docstrings dict from the module
import re
with open(enhancements_path, 'r') as f:
content = f.read()
# Extract the docstring for this class
pattern = f'"{class_name}":\\s*\'"""([^"]*?)"""\''.replace('"""', '"""')
match = re.search(f'"{class_name}":\\s*\'"""(.+?)"""\'', content, re.DOTALL)
if match:
enhanced_doc = match.group(1)
# Replace the lines with the enhanced docstring
lines.clear()
lines.extend(enhanced_doc.split('\\n'))
print(f"✓ Enhanced {name}")
except Exception as e:
pass # Silently fail for individual classes
# Connect to autodoc-process-docstring event
app.connect('autodoc-process-docstring', apply_enhancements_after_import)
print("✓ PySNT docstring enhancement hook registered")