|
| 1 | +# Extending mxdev with Hooks |
| 2 | + |
| 3 | +The functionality of mxdev can be extended by hooks. |
| 4 | +This is useful to generate additional scripts or files or automate any other setup steps related to mxdev's domain. |
| 5 | + |
| 6 | +## Configuration |
| 7 | + |
| 8 | +Extension configuration settings end up in the `mx.ini` file. |
| 9 | +They can be added globally to the `settings` section, as dedicated config sections or package specific. |
| 10 | +To avoid naming conflicts, all hook-related settings and config sections must be prefixed with a namespace. |
| 11 | + |
| 12 | +It is recommended to use the package name containing the hook as a namespace. |
| 13 | + |
| 14 | +This looks like so: |
| 15 | + |
| 16 | +```INI |
| 17 | +[settings] |
| 18 | +myextension-global_setting = 1 |
| 19 | + |
| 20 | +[myextension-section] |
| 21 | +setting = value |
| 22 | + |
| 23 | +[foo.bar] |
| 24 | +myextension-package_setting = 1 |
| 25 | +``` |
| 26 | + |
| 27 | +## Implementation |
| 28 | + |
| 29 | +The extension is implemented as a subclass of `mxdev.Hook`: |
| 30 | + |
| 31 | +```Python |
| 32 | +from mxdev import Hook |
| 33 | +from mxdev import State |
| 34 | + |
| 35 | +class MyExtension(Hook): |
| 36 | + |
| 37 | + namespace = "myextension" |
| 38 | + """The namespace for this hook.""" |
| 39 | + |
| 40 | + def read(self, state: State) -> None: |
| 41 | + """Gets executed after mxdev read operation.""" |
| 42 | + # Access configuration from state |
| 43 | + # - state.configuration.settings: main [settings] section |
| 44 | + # - state.configuration.packages: package sections |
| 45 | + # - state.configuration.hooks: hook-related sections |
| 46 | + |
| 47 | + # Example: Access your hook's settings |
| 48 | + global_setting = state.configuration.settings.get('myextension-global_setting') |
| 49 | + |
| 50 | + # Example: Access hook-specific sections |
| 51 | + for section_name, section_config in state.configuration.hooks.items(): |
| 52 | + if section_name.startswith('myextension-'): |
| 53 | + # Process your hook's configuration |
| 54 | + pass |
| 55 | + |
| 56 | + def write(self, state: State) -> None: |
| 57 | + """Gets executed after mxdev write operation.""" |
| 58 | + # Generate additional files, scripts, etc. |
| 59 | + # Access generated requirements/constraints from: |
| 60 | + # - state.requirements |
| 61 | + # - state.constraints |
| 62 | +``` |
| 63 | + |
| 64 | +## State Object |
| 65 | + |
| 66 | +The `State` object passed to hooks contains: |
| 67 | + |
| 68 | +- **`state.configuration`**: Configuration object with: |
| 69 | + - `settings`: Dict of main [settings] section |
| 70 | + - `packages`: Dict of package sections |
| 71 | + - `hooks`: Dict of hook-related sections |
| 72 | + |
| 73 | +- **`state.requirements`**: List of requirement lines (after write phase) |
| 74 | + |
| 75 | +- **`state.constraints`**: List of constraint lines (after write phase) |
| 76 | + |
| 77 | +## Registration |
| 78 | + |
| 79 | +The hook must be registered as an entry point in the `pyproject.toml` of your package: |
| 80 | + |
| 81 | +```TOML |
| 82 | +[project.entry-points.mxdev] |
| 83 | +myextension = "mypackage:MyExtension" |
| 84 | +``` |
| 85 | + |
| 86 | +Replace: |
| 87 | +- `myextension`: The name users will reference |
| 88 | +- `mypackage`: Your Python package name |
| 89 | +- `MyExtension`: Your Hook subclass |
| 90 | + |
| 91 | +## Hook Lifecycle |
| 92 | + |
| 93 | +1. **Read phase**: mxdev reads configuration and fetches sources |
| 94 | + - All hooks' `read()` methods are called |
| 95 | + |
| 96 | +2. **Write phase**: mxdev writes requirements and constraints |
| 97 | + - All hooks' `write()` methods are called |
| 98 | + |
| 99 | +## Namespace Convention |
| 100 | + |
| 101 | +- Use your package name as namespace prefix |
| 102 | +- All settings: `namespace-setting_name` |
| 103 | +- All sections: `[namespace-section]` |
| 104 | +- This prevents conflicts with other hooks |
| 105 | + |
| 106 | +## Example Use Cases |
| 107 | + |
| 108 | +- Generate additional configuration files (e.g., buildout.cfg, docker-compose.yml) |
| 109 | +- Create wrapper scripts for development |
| 110 | +- Generate IDE project files |
| 111 | +- Export dependency graphs |
| 112 | +- Integrate with other tools (pytest, tox, pre-commit) |
| 113 | + |
| 114 | +## Best Practices |
| 115 | + |
| 116 | +1. **Fail gracefully**: If your hook can't complete, log warnings instead of raising exceptions |
| 117 | +2. **Document your settings**: Provide clear documentation for all configuration options |
| 118 | +3. **Use namespaces**: Always prefix settings with your namespace |
| 119 | +4. **Be minimal**: Don't add heavy dependencies to your hook package |
| 120 | +5. **Test thoroughly**: Hooks run after mxdev's core operations, ensure they don't break workflows |
| 121 | + |
| 122 | +## See Also |
| 123 | + |
| 124 | +- [mxdev main documentation](README.md) |
| 125 | +- [Hook base class source](src/mxdev/hooks.py) |
| 126 | +- [State object source](src/mxdev/state.py) |
0 commit comments