Development tools and templates for PySNT.
- Curate vs Extended classes: In theory, all SNT classes would be treated the same. In practice, we split them in two
groups:
- Curate classes: Most commonly used. These are 1st class citizens with functional autocompletion
- Extended classes: These are either 'problematic' classes (e.g., objects that cannot be constructed in headless env.).
These won't have much of auto-completion. Users need to use java_utils to explore extended classes:
import pysnt pysnt.initialize() # Get all methods from TreeStatistics methods = pysnt.get_methods('TreeStatistics') # Find all members containing 'length' results = pysnt.find_members('TreeStatistics', 'length') # Get static fields (constants) constants = pysnt.get_fields('TreeStatistics', static_only=True)
- Package names: Follow Java package structure (
sc.fiji.snt.analysis.morphology) - Module paths: Follow Python import structure (
pysnt.analysis.morphology) - Class names: Use exact Java class names. Reflection converters use underscore notation (OuterClass_InnerClass)
- Module docstring: Include link to Javadoc package summary
placeholder_template.py: Is the template for creating new PySNT module__init__.pyfiles with consistent structure- See the
dev/scripts/directory for build and deployment scripts - Use
dev/scripts/extract_class_signatures.pyanddev/scripts/generate_stubs.pyfor type stub generation
Basic examples:
# Create a basic morphology analysis module
python dev/create_module.py analysis.morphology
# Create nested modules
python dev/create_module.py analysis.morphology.advancedWhat this creates:
- init.py files that now need to be manually edited
Examples with Class Lists:
python dev/create_module.py analysis.morphology \
--curated Class1Name Class2Name Class3Name \
--extended Class4Name Class5Name Class6NameWhat this creates:
- Module:
src/pysnt/analysis/morphology/__init__.py(Java package: sc.fiji.snt.analysis.morphology) - Curated classes (always available): Class1Name, Class2Name, Class3Name
- Extended classes (on-demand): Class4Name, Class5Name, Class6Name
Examples with Inner Classes:
python dev/create_module.py analysis.morphology \
--curated Class1Name_Config \
--extended Class2Name_ConfigWhat this creates:
- Module:
src/pysnt/analysis/morphology/__init__.py(Java package: sc.fiji.snt.analysis.morphology) - Curated classes (always available): Class1Name$Config
- Extended classes (on-demand): Class2Name$Config
After creating any module:
- Edit the generated file
- Update docstrings
- Remove template sections:
- Delete the template checklist
- Generate type stubs:
python dev/scripts/extract_class_signatures.py --all-classes --verbose # Extract signatures python dev/scripts/generate_stubs.py --verbose --overwrite # Generate stubs
- Test the module:
python -c "import src.pysnt.analysis.morphology; print('Module works!')"
- Create Module Structure
# Example: Adding morphology analysis module
mkdir -p src/pysnt/analysis/morphology- Copy Template
cp dev/placeholder_template.py src/pysnt/analysis/morphology/__init__.py- Configure Module
Edit the new
__init__.pyfile:
# 1. Update module docstring
# 2. Fill-in the lists of public classes in the package
CURATED_CLASSES = [
"MorphologyAnalyzer", # Most important classes
"ShapeMetrics",
"BranchAnalyzer"
]
EXTENDED_CLASSES = [
"AdvancedMorphology", # Less common classes
"DetailedMetrics",
"InternalUtils"
]
# Set package name
_module_funcs = setup_module_classes(
package_name="sc.fiji.snt.analysis.morphology", # ← Edit this
curated_classes=CURATED_CLASSES,
extended_classes=EXTENDED_CLASSES,
globals_dict=globals(),
)
# Set module path
__getattr__ = _module_funcs['create_getattr']('pysnt.analysis.morphology') # ← Edit this- Generate Type Stubs
python dev/scripts/extract_class_signatures.py --all-classes --verbose
python dev/scripts/generate_stubs.py --verbose --overwrite- Test
python -c "
import src.pysnt.new.module as mod
print(f'Curated: {mod.get_curated_classes()}')
print(f'Extended: {mod.get_extended_classes()}')
print(f'Total: {len(mod.get_available_classes())} classes')
"# Generate type stubs and test
python dev/scripts/generate_stubs.py --verbose
python -c "
import src.pysnt.new.module as mod
print('All tests passed!')
print(f'Module ready with {len(mod.get_available_classes())} classes')
"- Update Parent Module (as needed)
If adding a submodule, update the parent's
__init__.py:
# In parent src/pysnt/analysis/__init__.py
from . import morphology
# Update __getattr__ to handle submodules
__getattr__ = _module_funcs['create_getattr']('pysnt.analysis', submodules=['morphology']) # ← Edit this# Minimal setup for simple modules
_module_funcs = setup_module_classes(
package_name="sc.fiji.snt.new.package",
curated_classes=CURATED_CLASSES,
extended_classes=EXTENDED_CLASSES,
globals_dict=globals(),
)# Advanced setup with additional options
_module_funcs = setup_module_classes(
package_name="sc.fiji.snt.new.package",
curated_classes=CURATED_CLASSES,
extended_classes=EXTENDED_CLASSES,
globals_dict=globals(),
discovery_packages=["sc.fiji.snt", "sc.fiji.snt.util"], # Search multiple packages
include_interfaces=True, # Include Java interfaces
)# For modules with submodules
from . import submodule1, submodule2
__getattr__ = _module_funcs['create_getattr']('pysnt.new.module', submodules=['submodule1', 'submodule2'])The handle_constructor_exceptions() function in src/pysnt/common_module.py provides a centralized way to handle Java constructor issues that arise from the Python-Java bridge.
-
Varargs Constructor Selection Problem: Python calls
Constructor(args, String...)with empty varargs instead ofConstructor(args)Example:MultiTreeStatistics(Collection)vsMultiTreeStatistics(Collection, String...) -
Overloaded Constructor Ambiguity Problem: Python can't distinguish between similar constructor signatures Example: Multiple constructors with different parameter types
-
Parameter Type Conversion Problem: Python types don't map correctly to expected Java types Example: Python list vs Java Collection vs Java Array
To add a new constructor exception handler, modify the handle_constructor_exceptions() function:
def handle_constructor_exceptions(java_class, class_name, *args, **kwargs):
# Existing MultiTreeStatistics case
if class_name == 'MultiTreeStatistics' and len(args) == 1 and len(kwargs) == 0:
return java_class(args[0], "all")
# NEW CASE: Add another case here
if class_name == 'TheProblematicClass' and the_condition:
# return fix here
return java_class(modified_args)
# No special handling needed
return None