Description
While working on #2326 where I need to pass a configuration option deep into the guts of fontTools, Behdad identified that fontTools has a general need for a facility to pass such configuration options from top-level API deep into the various modules that use the options.
Summary of desired features:
- Read conf from a file
- Get conf from tools that rely on fontTools (ufo2ft/fontmake/...)
- Get conf from scripts that call fontTools APIs
- Have global / default values
- Have per-TTFont values (TTFont.config attribute)
- Use conf in any fontTools module
- Potentially have a cascading fallback system (see below)
Opinions so far:
Behdad: I'm suggesting that we add a cfg parameter to TTFont such that various modules can be configured. Eg. by configuring "fontTools.otlLib.optimize.gpos.optimize = 9"; ideally that would be inherited / cascade, such that if not set, it will fall back to "fontTools.otlLib.optimize" to "fontTools.optimize" to "optimize". Something like that. Maybe "optimize" is not the best name and compressionLevel is (for 1-9). etc. But that's the idea.
Cosimo:
- I think I'd prefer not to add a third-party dependency and try using built-in configparser only
- I think we can simply define one configparser section for each modue that has any configuration (e.g. "otLib.optimize.gpos", omitting the leading "fontTools"), containing the name=value for the module-level global variables, e.g. "optimize = 9"
- we define the configparser logic in a new fontTools.config module, initialized every time the fontTools top-level module is imported
- individual TTFonts can have a copy of the global configuration in that
cfg
attribute you suggested, so one could override individual settings at the font level - we search for a "fonttools.cfg" first in the same fontTools package directory (we embed a pre-populated default config in the installed package), then in default locations (e.g. ~/.config/fonttools/ or ~/.local/share/fonttools/, /usr/local/share/fonttools/, etc.)
- the fontTools.config.Config class could be a dict-like object where the keys (sections) are the names of the modules (without "fontTools." prefix), the values other dicts keyed by the variable name to be configured
- and the string values parsed to int, float, bool, etc.
- About exposing it to higher-up modules, most of them have an entrypoint that builds a new TTFont from scratch, so doesn't have a TTFont instance where to put the options at first, so we need a nice way to pass the options from that entrypoint (e.g. ufo2ft's compile() or varLib's main) down to the TTFont when it starts to exist
Simon: The idea behind cascading fallback is that if you have "otlLib.optimize = 5" and "otlLib.optimize.gpos = 9", then cfg.get("otlLib.optimize.gsub")
will return 5.
Behdad: We can keep it simple. If each module declares what its options default to, then we won't need cascading. What I want to avoid is every font build project having to specify a dozen long options. They shouldn't.
My opinion: I think this should also be an opportunity to document the available options.
Potential implementations:
- https://docs.python.org/3/library/configparser.html has DEFAULTs. But no hierarchy.
- https://omegaconf.readthedocs.io/en/2.0_branch/usage.html
- daltonmaag@8a2e34e written by me using advice from Cosimo
@behdad @simoncozens @anthrotype Please check that I have represented your opinions accurately :)
What does everyone think about this?