Package: jinjanator-plugins
jinjanator-plugins: API package for Jinjanator pluginsJinjanator can be extended through the use of plugins; these are
Python packages installed into the same environment as the tool
itself, which use special markers to 'hook' into various
features. There is a minimal example in theplugin_exampledirectory which demonstrates three of
the four possible hooks.For a more complete example, intended for publication, check out thejinjanator-plugin-ansiblerepository.Plugins can provide:formats: data parsers used to extract data from input files.filters: functions used in Jinja2 templates to transform data.tests: functions used in Jinja2 templates to make decisions in
conditional logic.globals: functions used in Jinja2 templates to obtain data from
external sources.For more details on the functionality and requirements for 'filters',
'tests', and 'globals', refer to the Jinja2 documentation.InstallationNormally there is no need to install this package; instead it should
be listed as one of the dependencies for the plugin package itself.Note: It isstronglyrecommended to pin the dependency for this
package to a specific version, or if not, a "year.release" version
range (like "23.2.*"). Failure to pin to a very narrow range of
versions may result in breakage of a plugin when the API is changed in
a non-backward-compatible fashion.This is somewhat similar tosemantic versioning, except not that :)Basic structureA minimal plugin consists of two files:pyproject.toml: provides package and project information, and
build instructionsPython source: provides functions to implement the desired features,
and hook markers to plug them into Jinjanatorpyproject.toml[build-system]build-backend="setuptools.build_meta"requires=["setuptools>=61",][project]name="jinjanator-plugin-example"version="0.0.0"dependencies=["jinjanator-plugins==23.3.0",][tool.setuptools]py-modules=["jinjanator_plugin_example"][project.entry-points.jinjanator]example="jinjanator_plugin_example"build-systemAny PEP517-compatible build system can be used, for this simple
examplesetuptoolsis being used.projectThenameandversionare required to build a package containing
the plugin. Note thespecific version dependencyon thejinjanator-pluginspackage.tool.setuptoolsThis section tellssetuptoolsto include a single source file (not a
package directory) into the distribution. Note that this is amodule
name, not afile name, so there is no.pyextension.project.entry-points.jinjanatorThis is the first part of the 'magic' mechanism which allows
Jinjanator to find the plugin. The entry here (which can use any name,
but should be related to the project name in order to avoid conflicts)
creates an 'entry point' which Jinjanator can use to find the plugin;
the value is the name of the module which Jinjanator should import to
find the plugin's hooks (and must match the name specified in thetool.setuptoolssection).jinjanator_plugin_example.pyimportcodecsfromjinjanator_pluginsimport(FormatOptionUnknownError,FormatOptionUnsupportedError,FormatOptionValueError,plugin_filters_hook,plugin_formats_hook,plugin_identity_hook,plugin_tests_hook,)defrot13_filter(value):returncodecs.encode(value,"rot13")defis_len12_test(value):returnlen(value)==12classSpamFormat:name="spam"suffixes=(".spam",)option_names=("ham",)def__init__(self,options):self.ham=Falseifoptions:foroptioninoptions:ifoption=="ham":self.ham=Truedefparse(self,data_string):ifself.ham:return{"ham":"ham","cheese":"ham and cheese","potatoes":"ham and potatoes",}return{"spam":"spam","cheese":"spam and cheese","potatoes":"spam and potatoes",}@plugin_identity_hookdefplugin_identities():return"example"@plugin_filters_hookdefplugin_filters():return{"rot13":rot13_filter}@plugin_tests_hookdefplugin_tests():return{"len12":is_len12_test}@plugin_formats_hookdefplugin_formats():return{SpamFormat.name:SpamFormat}Note that the real example makes use of type annotations, but they
have been removed here for simplicity.ImportsThe imports fromjinjanator_pluginsare necessary for the plugin to:Mark the hooks it wishes to use to provide additional features.Construct one (or more)Formatobjects to describe the formats it
supports, if any.Raise option-related exceptions from its format function, if any.rot13_filterA simple filter function which applies therot13transformation to
the string value it receives.is_len12_testA simple test function which returnsTrueif the value it receives
has length 12.SpamFormatA class providing a simple format function which ignores the content
provided (which Jinjanator would have read from a data file), and
instead returns one of two canned responses based on whether thehamoption has been provided by the user.Theparsemethod is the function which does the work; the__init__method handles options provided by the user; the class attributes
provide details of the format.plugin_identitiesThe hook function which will be called by Jinjanator to allow this
plugin to identify itself; the@plugin_identities_hookdecorator
marks the function so that it will be found.The function must return a string, which can contain any information
needed to identify the plugin. This should include the plugin's name
and version, and can include versions of any packages on which it
depends.This string will be included in the output generated byjinjanate --version, so it should not include any newlines or other formatting
characters.Note that the functionmustbe namedplugin_identities; it is the
second part of the 'magic' mechanism mentioned above.plugin_filtersThe hook function which will be called by Jinjanator to allow this
plugin to register any filter functions it provides; the@plugin_filters_hookdecorator marks the function so that it will be
found.The function must return a dictionary, with each key being a filter
function name (the name which will be used in the Jinja2 template to
invoke the function) and the corresponding value being a reference to
the function itself.Note that the functionmustbe namedplugin_filters; it is the
second part of the 'magic' mechanism mentioned above.plugin_testsThe hook function which will be called by Jinjanator to allow this
plugin to register any test functions it provides; the@plugin_tests_hookdecorator marks the function so that it will be
found.The function must return a dictionary, with each key being a test
function name (the name which will be used in the Jinja2 template to
invoke the function) and the corresponding value being a reference to
the function itself.Note that the functionmustbe namedplugin_tests; it is the
second part of the 'magic' mechanism mentioned above.plugin_formatsThe hook function which will be called by Jinjanator to allow this
plugin to register any format functions it provides; the@plugin_formats_hookdecorator marks the function so that it will be
found.The function must return a dictionary, with each key being a format
function name (the name which will be used in the--formatargument
to Jinjanator, if needed) and the corresponding value being a class
which implements the requirements of theFormatprotocol (defined ininit.py).In particular these requirements include:a class attribute callednamewhich contains the name of the
format (for use in error messages)a class attribute called 'suffixes' which contains a (possibly
empty) list of file suffixes which can be matched to this format
during format auto-detectiona class attribute called 'option_names' which contains a (possibly
empty) list of options which the user can provide to modify the
parser's behaviora constructor method (__init__) which accepts a (possibly empty)
list of options provided by the user, and performs any validation
needed on them, storing the results in theselfobjectaparsemethod which accepts a (possibly empty) string containing
the input data, and parses it according to the format's needs, using
any previously-validated options stored in theselfobjectNote that the hook functionmustbe namedplugin_formats; it is the
second part of the 'magic' mechanism mentioned above.Format classes can accept 'options' to modify their behavior, and
should raise the exceptions listed below, when needed, to inform the
user if one of the provided options does not meet the format's
requirements.FormatOptionUnknownErrorwill be raised automatically by the
Jinjanator CLI based on the content of theoption_namesattribute of
the format class.FormatOptionUnsupportedErrorshould be raised when a provided
option is not supported in combination with the other provided
options or with the parsed data.FormatOptionValueErrorshould be raised when a provided option has
a value that is not valid.Release InformationAdditionsAdded Python 3.12 support.
[#9](https://github.com/kpfleming/jinjanator-plugins/issues/9)→ Full Changelog