uniffi_bindgen 0.31.1

a multi-language bindings generator for rust (codegen and cli tooling)
Documentation
{#
# Python has a built-in `enum` module which is nice to use, but doesn't support
# variants with associated data. So, we switch here, and generate a stdlib `enum`
# when none of the variants have associated data, or a generic nested-class
# construct when they do.
#}
{%- let type_name = e.self_type.type_name %}
{%- let uniffi_trait_methods = e.uniffi_trait_methods %}

{% if e.is_flat %}

class {{ type_name }}(enum.Enum):
    {{ e.docstring|docstring(4) -}}
    {%- for variant in e.variants %}
    {{ variant.name }} = {{ variant.discr.py_lit }}
    {{ variant.docstring|docstring(4) -}}
    {% endfor %}

    {%- for meth in e.methods -%}
    {%- let callable = meth.callable %}
    {% if callable.is_async %}async {% endif %}def {{ callable.name }}(self, {% include "CallableArgs.py" %}) -> {{ callable.return_type.type_name }}:
        {{ meth.docstring|docstring(8) -}}
        {%- filter indent(8) %}
        {%- include "CallableBody.py" %}
        {%- endfilter %}
    {%- endfor %}
{% else %}

class {{ type_name }}:
    {{ e.docstring|docstring(4) -}}
    def __init__(self):
        raise RuntimeError("{{ type_name }} cannot be instantiated directly")

    # Each enum variant is a nested class of the enum itself.
    {% for variant in e.variants -%}
    @dataclass
    class {{ variant.name }}:
        {{ variant.docstring|docstring(8) -}}

    {%-  if variant.has_unnamed_fields() %}
        def __init__(self, *values):
            if len(values) != {{ variant.fields.len() }}:
                raise TypeError(f"Expected {{ variant.fields.len() }} arguments, found {len(values)}")
            self._values = values

        def __getitem__(self, index):
            return self._values[index]

    {% filter indent(8) %}
    {% include "UniffiTraitImpls.py" %}
    {% endfilter %}

    {# "builtin" methods not handled by a uniffi_trait #}
    {%-     if uniffi_trait_methods.display_fmt.is_none() %}
        def __str__(self):
            return f"{{ type_name }}.{{ variant.name }}{self._values!r}"
    {%-     endif %}

    {%-     if uniffi_trait_methods.eq_eq.is_none() %}
        def __eq__(self, other):
            if not isinstance(other, {{ type_name }}):
                return NotImplemented
            if not other.is_{{ variant.name }}():
                return False
            return self._values == other._values
    {%-     endif %}

    {%-  else %}
        def __init__(self, {% for field in variant.fields %}
        {{- field.name }}: {{- field.ty.type_name}}
        {%- if let Some(default) = field.default %} = {{ default.arg_literal }}{% endif %}
        {%- if !loop.last %}, {% endif %}
        {%- endfor %}):
        {%- for field in variant.fields %}
            {%- match field.default %}
            {%- when None %}
            self.{{ field.name }} = {{ field.name }}
            {%- when Some(default) %}
            {%- if default.is_arg_literal %}
            self.{{ field.name }} = {{ field.name }}
            {%- else %}
            if {{ field.name }} is {{ default.arg_literal }}:
                self.{{ field.name }} = {{ default.py_default }}
            else:
                self.{{ field.name }} = {{ field.name }}
            {%- endif %}
            {%- endmatch %}
            {# not sure what to do with docstrings - see #2552, suggests putting them after the field init #}
            {{ field.docstring|docstring(8) -}}
        {%- endfor %}
            pass

    {% filter indent(8) %}
    {%      include "UniffiTraitImpls.py" %}
    {% endfilter %}
    {# "builtin" methods not handled by a uniffi_trait #}
    {%-     if uniffi_trait_methods.display_fmt.is_none() %}
        def __str__(self):
            return "{{ type_name }}.{{ variant.name }}({% for field in variant.fields %}{{ field.name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields %}self.{{ field.name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
    {%-     endif %}
    {%-     if uniffi_trait_methods.eq_eq.is_none() %}
        def __eq__(self, other):
            if not isinstance(other, {{ type_name }}):
                return NotImplemented
            if not other.is_{{ variant.name }}():
                return False
            {%- for field in variant.fields %}
            if self.{{ field.name }} != other.{{ field.name }}:
                return False
            {%- endfor %}
            return True
    {%-     endif %}
    {%-  endif %}

    {% endfor %}

    # For each variant, we have `is_NAME` and `is_name` methods for easily checking
    # whether an instance is that variant.
    {% for variant in e.variants -%}
    def is_{{ variant.name }}(self) -> bool:
        return isinstance(self, {{ type_name }}.{{ variant.name }})

    {#- We used to think we used `is_NAME` but did `is_name` instead. In #2270 we decided to do both. #}
    {%- if variant.name != variant.name.to_snake_case() %}
    def is_{{ variant.name.to_snake_case() }}(self) -> bool:
        return isinstance(self, {{ type_name }}.{{ variant.name }})
    {%- endif %}
    {% endfor %}

    {%- for meth in e.methods -%}
    {%- let callable = meth.callable %}
    {% if callable.is_async %}async {% endif %}def {{ callable.name }}(self, {% include "CallableArgs.py" %}) -> {{ callable.return_type.type_name }}:
        {{ meth.docstring|docstring(8) -}}
        {%- filter indent(8) %}
        {%- include "CallableBody.py" %}
        {%- endfilter %}
    {%- endfor %}

# Now, a little trick - we make each nested variant class be a subclass of the main
# enum class, so that method calls and instance checks etc will work intuitively.
# We might be able to do this a little more neatly with a metaclass, but this'll do.
{% for variant in e.variants -%}
{{ type_name }}.{{ variant.name }} = type("{{ type_name }}.{{ variant.name }}", ({{ type_name }}.{{variant.name}}, {{ type_name }},), {})  # type: ignore
{% endfor %}

{% endif %}

class {{ e.self_type.ffi_converter_name }}(_UniffiConverterRustBuffer):
    @staticmethod
    def read(buf):
        variant = buf.read_i32()

        {%- for variant in e.variants %}
        if variant == {{ loop.index }}:
            {%- if e.is_flat %}
            return {{ type_name }}.{{variant.name}}
            {%- else %}
            return {{ type_name }}.{{variant.name}}(
                {%- for field in variant.fields %}
                {{ field.ty.ffi_converter_name }}.read(buf),
                {%- endfor %}
            )
            {%- endif %}
        {%- endfor %}
        raise InternalError("Raw enum value doesn't match any cases")

    @staticmethod
    def check_lower(value):
        {%- if e.variants.is_empty() %}
        pass
        {%- else %}
        {%- for variant in e.variants %}
        {%- if e.is_flat %}
        if value == {{ type_name }}.{{ variant.name }}:
        {%- else %}
        if value.is_{{ variant.name }}():
        {%- endif %}
            {%- for field in variant.fields %}
            {%- if variant.has_unnamed_fields() %}
            {{ field.ty.ffi_converter_name }}.check_lower(value._values[{{ loop.index0 }}])
            {%- else %}
            {{ field.ty.ffi_converter_name }}.check_lower(value.{{ field.name }})
            {%- endif %}
            {%- endfor %}
            return
        {%- endfor %}
        raise ValueError(value)
        {%- endif %}

    @staticmethod
    def write(value, buf):
        {%- for variant in e.variants %}
        {%- if e.is_flat %}
        if value == {{ type_name }}.{{ variant.name }}:
            buf.write_i32({{ loop.index }})
        {%- else %}
        if value.is_{{ variant.name }}():
            buf.write_i32({{ loop.index }})
            {%- for field in variant.fields %}
            {%- if variant.has_unnamed_fields() %}
            {{ field.ty.ffi_converter_name }}.write(value._values[{{ loop.index0 }}], buf)
            {%- else %}
            {{ field.ty.ffi_converter_name }}.write(value.{{ field.name }}, buf)
            {%- endif %}
            {%- endfor %}
        {%- endif %}
        {%- endfor %}