feature:refine
Table of contents
- Introduction
- Choosing the base and its points
- Sharing a base with other refiners
- What it writes
- Running it on code you already wrote
- Reference
- Requirements
- See also
Introduction
feature:refine turns a feature into a refiner of another one, through an interactive wizard. You pick the base and its extension points, and the wizard declares the refinement, scaffolds the code and adds the refiner to your product. Check first whether filling the base’s hook slots is enough (see Refine, extend or compose).
splent feature:refine splent_feature_notes_tags
Step 1: Which feature do you want to refine?
1. notes
1 service(s), 1 model(s), 3 template(s), 1 hook(s), routes
Select: 1
...
Select models to extend (space-separated numbers, or 0 for none) [0]: 1
mixin: Notes <- NotesTagsMixin
...
Write to pyproject.toml and scaffold code? [Y/n]: y
pyproject.toml updated.
scaffolded: models.py <- NotesTagsMixin
scaffolded: __init__.py
...
done.
Choosing the base and its points
Answer Select with a number or a name (notes or splent_feature_notes). Then answer each kind of point with numbers separated by spaces, or 0 for none. The contracts of your editable features are refreshed first.
| Point | What the refiner does |
|---|---|
| Models | Extends them with a mixin |
| Services | Overrides them with a replacement class |
| Templates | Overrides them |
| Hook slots the base offers | Fills them with register_template_hook |
| Hooks the base registers | Overrides them with replace_template_hook |
| Routes | Adds them to the base’s blueprint (yes or no) |
A refiner is never offered as a base, since chains of refiners are not supported.
Sharing a base with other refiners
When other refiners of the base are in your product, the wizard lists what each one claims.
Refining: shop
Other refiners of shop
collections
extends model Item, adding collection_id
shop_staff
extends model Item, adding created_by
A taken target is marked (taken by <refiner>) and cannot be chosen.
| Target | Taken when |
|---|---|
| A template or a service | Any refiner of the product overrides it |
| A hook | Another refiner of the same base overrides it |
| A model | Never. It shows who else extends it |
Two refiners of one model must add attributes with different names, which product:validate checks.
What it writes
The refinement goes into the refiner’s pyproject.toml, after a backup.
[tool.splent.refinement]
refines = "splent_feature_notes"
[tool.splent.refinement.extends]
models = [{ target = "Notes", mixin = "NotesTagsMixin" }]
[tool.splent.refinement.overrides]
services = [{ target = "NotesService", replacement = "NotesServiceWithTags" }]
Then it scaffolds the code.
| File | What you get |
|---|---|
models.py |
The mixin classes, instead of the scaffold’s db.Model |
services.py |
The replacement classes. repositories.py becomes a comment |
__init__.py |
The blueprint, refine_model() and refine_service() in init_feature, and inject_context_vars |
hooks.py |
One *args, **kwargs stub per slot you fill (register_template_hook) or hook you override (replace_template_hook) |
templates/hooks/<slot>.html |
One per stub, dots turned into underscores. Put the markup there |
routes_<refiner>.py |
A sample view on the base blueprint. Import only that blueprint at module level |
config.py, signals.py |
A one-line comment, unless they hold your code |
migrations/env.py |
Untouched. The framework migrates the injected columns |
Class names join the target with the refiner’s name minus the base’s, so notes_tags gets NotesTagsMixin and NotesServiceWithTags. Last, the wizard updates the refiner’s contract and runs feature:add.
Running it on code you already wrote
The wizard overwrites a code file without asking only when it is absent, holds only comments, pass or a docstring, or is still what feature:create wrote. Anything else gets Overwrite it?, which defaults to No.
A
hooks.pythat already registers hooks is kept. Add thereplace_template_hook("<slot>", ...)call of a new override yourself, or startup warns that it is never applied.
Reference
Arguments
| Argument | Description |
|---|---|
REFINER_NAME |
The feature that becomes the refiner, such as splent_feature_notes_tags |
Errors
| Message | What to do |
|---|---|
<refiner> not found in workspace. |
Check the name |
<x> refines <refiner>, so <refiner> cannot refine another feature. |
Pick another refiner |
<refiner> is named after the product ... |
Rename it (see Naming) |
Refinement already configured (refines: ...) |
Answer Overwrite? with yes |
No features with extensible points found in this product. |
Declare [tool.splent.contract.extensible] in the base |
Nothing selected. Aborting. |
Choose at least one point |
Refusing to write pyproject.toml: the result is not valid TOML. |
Fix it by hand |
All but Refinement already configured and Nothing selected exit 1.
Requirements
- A selected product (
SPLENT_APP). - An editable refiner, at the workspace root or behind the product’s
features/link. Runfeature:unlockon a pinned one first. - A base with at least one extension point in
[tool.splent.contract.extensible].
See also
feature:xraymaps composition and refinement.product:validatechecks that refiners do not collide.- Tutorial 6, Extend with refinement walks through it step by step.