Custom functions¶
You can add functions to the ones available to FTL authors by passing a
functions
dictionary to the FluentBundle
constructor or to
compile_messages
:
>>> import platform
>>> def os_name():
... """Returns linux/mac/windows/other"""
... return {'Linux': 'linux',
... 'Darwin': 'mac',
... 'Windows': 'windows'}.get(platform.system(), 'other')
>>> bundle = FluentBundle.from_string(
... 'en-US',
... """
... welcome = { OS() ->
... [linux] Welcome to Linux
... [mac] Welcome to Mac
... [windows] Welcome to Windows
... *[other] Welcome
... }
... """,
... functions={'OS': os_name})
>>> print(bundle.format('welcome')[0]
Welcome to Linux
These functions can accept positional and keyword arguments, like the NUMBER
and DATETIME
builtins. They must accept the following types of objects
passed as arguments:
str
fluent_compiler.types.FluentType
subclasses, namely:FluentNumber
-int
,float
orDecimal
objects passed in externally, or expressed as literals, are wrapped in these. Note that these objects also subclass builtinint
,float
orDecimal
, so can be used as numbers in the normal way.FluentDateType
-date
ordatetime
objects passed in are wrapped in these. Again, these classes also subclassdate
ordatetime
, and can be used as such.FluentNone
- in error conditions, such as a message referring to an argument that hasn’t been passed in, objects of this type are passed in.
Custom functions should not raise exceptions, but return FluentNone
instances to
indicate an error or missing data. Otherwise they should return strings,
or instances of a FluentType
subclass as above. Returned numbers and
datetimes should be converted to FluentNumber
or FluentDateType
subclasses using fluent.types.fluent_number
and fluent.types.fluent_date
respectively.
If your function does raise exceptions fluent_compiler
will not catch them -
they will propagate up and cause the formatting of the message to raise that
exception. This is intended behaviour, to aid debugging of the broken custom
function.
The type signatures of custom functions are checked before they are used, to
ensure the right the number of positional arguments are used, and only available
keyword arguments are used - otherwise a TypeError
will be appended to the
errors
list. Using *args
or **kwargs
to allow any number of
positional or keyword arguments is supported, but you should ensure that your
function actually does allow all positional or keyword arguments.
If you want to override the detected type signature (for example, to limit the
arguments that can be used in an FTL file, or to provide a proper signature for
a function that has a signature using *args
and **kwargs
but is more
restricted in reality), you can add an ftl_arg_spec
attribute to the
function. The value should be a two-tuple containing 1) an integer specifying
the number of positional arguments, and 2) a list of allowed keyword arguments.
For example, for a custom function my_func
the following will stop the
restricted
keyword argument from being used from FTL files, while allowing
allowed
, and will require that a single positional argument is passed:
def my_func(arg1, allowed=None, restricted=None):
pass
my_func.ftl_arg_spec = (1, ['allowed'])
The Fluent spec allows keyword arguments with hyphens (-
) in them. These are
not valid identifiers in Python, so if you need to a custom function to accept
keyword arguments like this, you will have to use **kwargs
syntax e.g.:
def my_func(kwarg1=None, **kwargs):
kwarg_with_hyphens = kwargs.pop('kwarg-with-hyphens', None)
# etc.
my_func.ftl_arg_spec = (0, ['kwarg1', 'kwarg-with-hyphens'])