Lowers the barrier for sister sites to build their own plugins. Generated output satisfies the framework contract out of the box. CLI command (shopdb/plugins/cli.py): - `flask plugin new <name> --description "..."` generates a plugin skeleton under plugins/<name>/. Validates the name against CONTRIBUTING.md rules (lowercase letters/digits only, no underscores or hyphens, not in the reserved list) and refuses to overwrite existing plugins unless --overwrite is passed. - Output prints the next steps (install, migrate, test). Scaffolder (shopdb/plugins/scaffolder.py): - validate_name: enforces the naming rules - pascal_case: lowercase-to-PascalCase for class names - scaffold_plugin: copies templates with string.Template substitution. Three placeholders: $name, $Name, $description. Files with `model.py` in the path get renamed to <name>.py. Templates (shopdb/plugins/templates/): - manifest.json.tmpl: name, version 0.1.0, description, core_version range >=0.1.0,<1.0.0 (broad enough to survive minor framework bumps) - plugin.py.tmpl: <Name>Plugin class extending BasePlugin with all required hooks implemented (meta from manifest, get_blueprint returning the bp, get_models returning the example model). Includes on_install hook that seeds the AssetType row. - models/__init__.py.tmpl + models/model.py.tmpl: Asset extension table keyed by assetid with one example field. TODO comment marks it as a placeholder. - api/__init__.py.tmpl + api/routes.py.tmpl: Blueprint with list and detail endpoints using the framework's pagination + response helpers. - schemas/__init__.py.tmpl: marshmallow schema stub. - tests/__init__.py.tmpl + tests/test_plugin.py.tmpl: smoke tests asserting plugin loads, get_blueprint returns Blueprint, get_models returns at least one model. - README.md.tmpl: one-pager for plugin authors with common edits and next-step references. Canary tests (tests/test_plugin_scaffold.py): - 14 tests asserting the scaffold output passes contract checks. - Validates name rules (lowercase, reserved, hyphens, digits, etc.) - Verifies all expected files generated, manifest fields present. - Loads the generated plugin via PluginLoader (spec_from_file_location bypasses the real `plugins` package shadowing). - Asserts subclasses BasePlugin, get_blueprint returns Blueprint, get_models returns model with __tablename__. - Module-scoped fixture; cleans up sys.modules + SQLAlchemy metadata on teardown to avoid cross-test contamination. Quickstart docs (docs/PLUGIN-QUICKSTART.md): - 30-minute walkthrough: scaffold -> edit model -> add routes -> install -> verify -> add hooks. Cross-links to PLUGIN-HOOKS.md and the ADRs. Includes common-errors table. Test count: 87 -> 101 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
45 lines
1.1 KiB
Cheetah
45 lines
1.1 KiB
Cheetah
"""$Name plugin API routes."""
|
|
|
|
from flask import Blueprint, request
|
|
from flask_jwt_extended import jwt_required
|
|
|
|
from shopdb.utils.responses import (
|
|
success_response,
|
|
error_response,
|
|
paginated_response,
|
|
ErrorCodes,
|
|
)
|
|
from shopdb.utils.pagination import get_pagination_params, paginate_query
|
|
|
|
from ..models import $Name
|
|
|
|
|
|
${name}_bp = Blueprint('$name', __name__)
|
|
|
|
|
|
@${name}_bp.route('', methods=['GET'])
|
|
@jwt_required(optional=True)
|
|
def list_${name}():
|
|
"""List $name assets, paginated."""
|
|
page, per_page = get_pagination_params(request)
|
|
|
|
query = ${Name}.query
|
|
items, total = paginate_query(query, page, per_page)
|
|
data = [item.to_dict() for item in items]
|
|
|
|
return paginated_response(data, page, per_page, total)
|
|
|
|
|
|
@${name}_bp.route('/<int:assetid>', methods=['GET'])
|
|
@jwt_required(optional=True)
|
|
def get_${name}(assetid: int):
|
|
"""Get a single $name by assetid."""
|
|
item = ${Name}.query.get(assetid)
|
|
if not item:
|
|
return error_response(
|
|
ErrorCodes.NOT_FOUND,
|
|
f'$Name with assetid {assetid} not found',
|
|
http_code=404,
|
|
)
|
|
return success_response(item.to_dict())
|