Getting Started with Extension Development¶
This guide walks you through creating your first halfORM extension that integrates with the hop
command. You'll learn the basics of the plugin system and build a simple but functional extension.
Prerequisites
- Familiarity with Python packaging (
setup.py
,pip install
) - Basic understanding of Click for command-line interfaces
- Knowledge of halfORM fundamentals (Quick Start)
- halfORM_dev installed (
pip install half-orm-dev
)
What We'll Build¶
We'll create half-orm-hello
, a simple extension that demonstrates:
- Plugin discovery and registration
- Integration with hop
commands
- Access to the current halfORM project context
- Extension configuration and metadata
The final result will provide these commands:
hop hello greet --name "World" # Prints "Hello, World!"
hop hello status # Shows project information
hop hello config # Manages extension settings
Project Setup¶
1. Create Extension Project Structure¶
mkdir half-orm-hello
cd half-orm-hello
# Create the standard extension structure
mkdir -p half_orm_hello
touch half_orm_hello/__init__.py
touch half_orm_hello/hop_extension.py
touch setup.py
touch README.md
Your structure should look like:
half-orm-hello/
├── half_orm_hello/
│ ├── __init__.py
│ └── hop_extension.py # Required: hop integration
├── setup.py
└── README.md
2. Package Configuration¶
Create setup.py
with the standard extension format:
from setuptools import setup, find_packages
setup(
name='half-orm-hello',
version='0.1.0',
description='Hello World extension for halfORM',
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
author='Your Name',
author_email='your.email@example.com',
url='https://github.com/yourusername/half-orm-hello',
license='GPL-3.0',
packages=find_packages(),
install_requires=[
'half-orm>=0.15.0',
'half-orm-dev>=0.1.0', # Required for hop integration
'click>=8.0.0', # For command-line interface
],
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Topic :: Database',
'Topic :: Software Development :: Libraries :: Python Modules',
],
python_requires='>=3.8',
)
Core Extension Implementation¶
3. Extension Entry Point¶
Create half_orm_hello/hop_extension.py
- this is the required integration point:
"""
hop extension integration for half-orm-hello
This module provides the entry point for the halfORM ecosystem plugin system.
The add_commands() function is called automatically when the extension is discovered.
"""
import click
from half_orm_dev.repo import Repo
def add_commands(hop_main_group):
"""
Required entry point for halfORM extensions.
This function is called automatically by hop's plugin discovery system.
It should add command groups to the main hop CLI.
Args:
hop_main_group: The main Click group for the hop command
"""
@click.group()
def hello():
"""Hello World extension commands"""
pass
@hello.command()
@click.option('--name', default='halfORM', help='Name to greet')
@click.option('--uppercase', is_flag=True, help='Use uppercase')
def greet(name, uppercase):
"""Greet someone with a friendly message"""
message = f"Hello, {name}!"
if uppercase:
message = message.upper()
click.echo(message)
@hello.command()
def status():
"""Show current project status"""
repo = Repo()
if not repo.checked:
click.echo("❌ Not in a halfORM project directory")
return
click.echo("✅ halfORM Project Information:")
click.echo(f" 📦 Project: {repo.name}")
click.echo(f" 📁 Base directory: {repo.base_dir}")
if hasattr(repo, 'database') and repo.database:
click.echo(f" 🗄️ Database: {repo.database.model.database}")
# Count relations
try:
relations = list(repo.database.model._relations())
click.echo(f" 📊 Relations: {len(relations)} tables/views")
except Exception as e:
click.echo(f" ⚠️ Database connection issue: {e}")
@hello.command()
@click.option('--set', 'set_value', nargs=2, help='Set configuration key=value')
@click.option('--get', 'get_key', help='Get configuration value')
@click.option('--list', 'list_all', is_flag=True, help='List all configuration')
def config(set_value, get_key, list_all):
"""Manage extension configuration"""
# Simple configuration example (in real extension, use proper config storage)
import os
config_file = os.path.expanduser("~/.half_orm_hello_config")
if set_value:
key, value = set_value
# Save configuration (simplified example)
with open(config_file, 'a') as f:
f.write(f"{key}={value}\n")
click.echo(f"✅ Set {key} = {value}")
elif get_key:
# Read configuration (simplified example)
try:
with open(config_file, 'r') as f:
for line in f:
if line.startswith(f"{get_key}="):
value = line.split('=', 1)[1].strip()
click.echo(f"{get_key} = {value}")
return
click.echo(f"❌ Key '{get_key}' not found")
except FileNotFoundError:
click.echo(f"❌ Key '{get_key}' not found (no config file)")
elif list_all:
try:
with open(config_file, 'r') as f:
click.echo("📋 Configuration:")
for line in f:
if '=' in line:
click.echo(f" {line.strip()}")
except FileNotFoundError:
click.echo("📋 No configuration found")
else:
click.echo("Use --set, --get, or --list to manage configuration")
# Register the command group with hop
hop_main_group.add_command(hello)
# Extension metadata (optional but recommended)
EXTENSION_INFO = {
'name': 'hello',
'version': '0.1.0',
'description': 'Hello World extension demonstrating halfORM plugin system',
'commands': ['hello'],
'author': 'halfORM Team',
'url': 'https://github.com/collorg/half-orm-hello'
}
4. Extension Package Initialization¶
Update half_orm_hello/__init__.py
:
"""
half-orm-hello: A Hello World extension for halfORM
This package demonstrates how to create extensions for the halfORM ecosystem.
"""
__version__ = '0.1.0'
__author__ = 'Your Name'
# Import main functionality if needed
from .hop_extension import EXTENSION_INFO
__all__ = ['EXTENSION_INFO']
Testing Your Extension¶
5. Install in Development Mode¶
# Install your extension in development mode
pip install -e .
# Verify hop discovers your extension
hop --help
You should see your hello
command listed:
Commands:
hello Hello World extension commands
new Creates a new hop project named <package_name>.
# ... other hop commands
6. Test Extension Commands¶
# Test the greet command
hop hello greet
# Output: Hello, halfORM!
hop hello greet --name "Developer" --uppercase
# Output: HELLO, DEVELOPER!
# Test status (outside a hop project)
hop hello status
# Output: ❌ Not in a halfORM project directory
# Create a test project and try again
hop new test_project --devel
cd test_project
hop hello status
# Output: ✅ halfORM Project Information...
# Test configuration
hop hello config --set greeting "Bonjour"
hop hello config --get greeting
# Output: greeting = Bonjour
hop hello config --list
# Output: 📋 Configuration:
# greeting=Bonjour
Understanding the Plugin System¶
How Discovery Works¶
halfORM_dev uses a simple but effective discovery mechanism:
- Package Scanning: Looks for installed packages matching
half-orm-*
- Module Import: Tries to import
{package_name}.hop_extension
- Function Call: Calls
add_commands(hop_main_group)
if it exists - Integration: Your commands become part of the hop CLI
Extension Loading Sequence¶
# In half_orm_dev/hop.py (simplified)
def discover_extensions():
extensions = {}
for dist in pkg_resources.working_set:
if dist.project_name.startswith('half-orm-') and dist.project_name != 'half-orm-dev':
try:
module_name = dist.project_name.replace('-', '_')
module = importlib.import_module(f'{module_name}.hop_extension')
if hasattr(module, 'add_commands'):
extensions[dist.project_name] = module
except ImportError:
continue
return extensions
# Your add_commands() function gets called here
for ext_name, ext_module in extensions.items():
ext_module.add_commands(main_group)
Access to hop Context¶
Your extension can access the current hop project context:
from half_orm_dev.repo import Repo
def some_command():
repo = Repo()
# Check if we're in a hop project
if not repo.checked:
click.echo("Not in a hop project")
return
# Access project information
project_name = repo.name
base_directory = repo.base_dir
# Access database if available
if repo.database:
model = repo.database.model
# Use halfORM functionality
relations = list(model._relations())
Best Practices¶
Command Organization¶
# ✅ Good: Organize related commands in groups
@click.group()
def myextension():
"""My extension commands"""
pass
@myextension.group()
def database():
"""Database-related commands"""
pass
@myextension.group()
def api():
"""API-related commands"""
pass
# Results in: hop myextension database init, hop myextension api generate, etc.
Error Handling¶
# ✅ Good: Handle errors gracefully
@hello.command()
def risky_operation():
try:
repo = Repo()
if not repo.checked:
click.echo("❌ Not in a halfORM project", err=True)
raise click.Abort()
# Your logic here
result = some_operation()
click.echo(f"✅ Success: {result}")
except SomeSpecificError as e:
click.echo(f"❌ Operation failed: {e}", err=True)
raise click.Abort()
except Exception as e:
click.echo(f"❌ Unexpected error: {e}", err=True)
click.echo("Please report this issue", err=True)
raise click.Abort()
Configuration Management¶
# ✅ Good: Use proper configuration storage
import os
import json
from pathlib import Path
def get_config_path():
"""Get extension configuration file path"""
config_dir = Path.home() / '.half_orm' / 'extensions' / 'hello'
config_dir.mkdir(parents=True, exist_ok=True)
return config_dir / 'config.json'
def load_config():
"""Load extension configuration"""
config_path = get_config_path()
if config_path.exists():
with open(config_path) as f:
return json.load(f)
return {}
def save_config(config):
"""Save extension configuration"""
config_path = get_config_path()
with open(config_path, 'w') as f:
json.dump(config, f, indent=2)
Next Steps¶
Congratulations! You've created your first halfORM extension. Here's what to explore next:
1. Enhanced Functionality¶
- Plugin API Reference - Learn advanced integration patterns
- Testing Guide - Add comprehensive tests to your extension
- Database Integration - Use halfORM's full power in your extension
2. Real-World Examples¶
Study existing extensions: - half-orm-litestar-api - Complex API generation - half-orm-instant-api - Database introspection and configuration
3. Distribution¶
- Publishing Guide - Share your extension with the community
- GitHub Actions - Set up CI/CD for your extension
- Documentation - Create comprehensive user guides
4. Advanced Topics¶
- Hooks and Events - Integrate with hop's lifecycle events
- Configuration Management - Advanced settings and user preferences
- Performance - Optimize for large projects
- Error Recovery - Handle edge cases gracefully
Common Issues and Solutions¶
Extension Not Discovered¶
Problem: hop --help
doesn't show your extension commands
Solutions:
1. Check package name starts with half-orm-
2. Verify hop_extension.py
exists and has add_commands()
function
3. Ensure extension is installed: pip list | grep half-orm-hello
4. Check for import errors: python -c "import half_orm_hello.hop_extension"
Import Errors¶
Problem: Extension fails to load with import errors
Solutions:
1. Check dependencies in setup.py
2. Verify module paths are correct
3. Test imports manually: python -c "from half_orm_hello import hop_extension"
Command Conflicts¶
Problem: Command names conflict with other extensions
Solutions:
1. Use descriptive, unique command group names
2. Namespace your commands: hop myextension subcommand
3. Check existing extensions for naming conflicts
Resources¶
- halfORM Fundamentals - Core ORM concepts
- Click Documentation - Command-line interface framework
- Plugin API Reference - Advanced integration patterns
- Extension Examples - Real-world extension code
Community¶
- Questions: GitHub Discussions
- Bug Reports: GitHub Issues
- Extension Ideas: Community Extensions
Ready to build something more complex? Continue to the Plugin API Reference for advanced integration patterns.