Python RPC bindings wishlist

Since a while now, the Delta Chat rust core has RPC bindings for python, supposed to replace the legacy CFFI bindings.

Right now we are in (to quote a famous man) “that nasty moment when the new lib is half-backed and experimental and the legacy lib is not good to use”. In theory, everything is possible with the new library, but the APIs are not very ergonomic yet, and because of the lack of documentation you need to look up rust source code a lot and use low-level API functions. Right now you need to be a Delta expert to use a library which is supposed to be used by Delta newcomers.

Improving the python library will probably take a few-days effort by 1-2 experienced people. So let’s use this thread to compile developers’ needs, especially from Delta newcomers, so we can write the best Delta Chat bindings python library that ever existed.

2 Likes

One thing I stumbled upon:

account.create_chat() is used only for testing and should be renamed. It’s not documented, but the method existed in the CFFI bindings, so some will probably use it nonetheless, and auto-complete in IDEs finds it.

The correct method to use is contact.create_chat(), so if you only have an account object, you need to create a contact first.

Alternatively, account.create_chat() could create a contact implicitly if it’s called with any input which also works for contact.create_chat(), that would make migrating from CFFI bindings to RPC bindings much easier.

In the old bindings, I always missed a page where you can view all the events and their attributes.

Hooking into incoming events is supposed to be the default way of how to make your bot react to Delta Chat users. But then they are documented nowhere, and if you want to create a function, you need to guess what parameters the event delivers. e.g. in the old echo bot example you could see:

    @account_hookimpl
    def ac_incoming_message(self, message):

But how were you supposed to know with which parameters the hook would be called if it was DC_EVENT_CONTACTS_CHANGED or something similar?

Now it’s slightly different:

@hooks.on(events.NewMessage)
def echo(event):
    snapshot = event.message_snapshot

This is even more confusing though. It’s good that you can hook on a class now, but only 4 of the events have their own class, so most of the time you need to listen for RawEvent. But how would I know if they also have a message_snapshot attached? Can they have other kinds of snapshots attached as well or is every event caused by a message?

It would be great if all events simply had a class and proper documentation about what attributes they have.

Which brings me to the next topic, snapshots.

In the old bindings, for each of the four classes you could see in the docs which attributes they offer, e.g. contacts clearly had an addr, a display_name, a name, and a last_seen timestamp.

If I want to access that now, I have to get a snapshot first; apparently this gives me an AttrDict object which I can not simply print out in the console or pdb to see what attributes it actually has, so I end up trying to look up in the rust code what attributes are probably there, and trial and error the python names of the attributes (camel case vs. underscore etc.).

Maybe we can just put this information into the documentation of get_snapshot()

Also: nowhere is explained what a snapshot is, what it’s good for, why I need it, and how it makes a difference when I request the snapshot. Personally, I’d rather have a high-level API where users don’t need to know about snapshots at all, and when they request contact.addr, the snapshot which contains the attribute is requested from the RPC server silently in the background. This is basically how it worked in the CFFI bindings, and it was much easier to get started developing this way.
Getter methods could also minimize exposing this to the user.

And one last thing which I also missed in the CFFI bindings already: a list of all the config options and their possible values.

Because I have been doing this for a while, I have bookmarked Config in deltachat::config - Rust and look there every time I need it. But in Rust the config keys use CamelCase, while in sqlite and in the python RPC library the words in config keys are separated by an underscore, so I always have to translate this. This should just be part of the python docs instead.

What would also be very interesting to know for developers: you can use the config table as a key value store by simply prepending ui. to the keys, e.g. if members in one chat group of your bot have special privileges, you can store that with account.set_config("ui.admin_group_id", str(chat.id)) without having to create a new database of your own. This is tremendously useful and I would have saved myself so much trouble if I had known this early on.

while it might seems nice, you don’t have a way to query existing ui. options so you would need to be really careful or you might end storing forever some private user data/metadata there, there is no easy way to cascade delete things there etc. so I still recommend to use your own sqlite database for that situation, ui. can be useful for other stuff tho, like bot account specific configurations, ex. bot/account language, customizable help message etc

the problem only arises from the use of such extra abstraction levels, if you used deltabot-cli which in turns uses deltachat2 lib, there is no such situations possible, with such objects just wrapping an integer you always have to hunt down where the method is: in the account object? in the contact object? if you used the RPC methods directly there is no such confusion, there you directly have a:

create_chat_by_contact_id
and
create_group_chat

the best place to look for config options and their documentation is the deltachat.h file, there you have the real config values as expected by core with underscore and the explanation of every possible value, their meaning and default values, indeed would be nice to have this as part of the bindings’s documentation, this is possible to do with the RPC bindings since you can specify the possible string values accepted by set_config and there each possible value could be documented as part of set_config documentation

2 Likes

alternatively, online and rendered at dc_set_config() (c.delta.chat is created from deltachat.h)

1 Like

snapshot is a term used to tell you that the object does not update automatically when the underlying chat changes, it is a snapshot, not the real thing. In the cffi many functions to get attributes/properties call the db directly so the data is always the latest/current version.

Not even the “low-level” api is nice, because it doesn’t have generated types and autocompletion like the lib for typescript does. Low level can be generated, high-level needs to be written and maintained manually.

depends, as long as they don’t call jsonrpc functions I might agree. But then you could just make typed python classes and parse the json data into those.

Is that generated or written manually?
There is a config key (sys.config_keys) that you can get that gives you all possible config keys at runtime, maybe we could generate some type definition from that. I also miss autocompletion & typechecking for the config keys in typescript bindings.

+💯 I’ll write that into my todo for the new bot docs page I’m working on.

That can be fatal If you don’t remember to copy it over when reseting the bot, we had some issue with the login bot for the forum in the past because we only reset one of the two databases. So if doing that you should try to store the db also in the folder of the DC account to minimise the risk of it, or avoid storing contact/chat ids in your custom db.

I think it would be nice if we could add some more utility functions for ui config keys, like to get a list of all of them, like you suggested.

Yeah high-level Object oriented api would need to be very good maintained and thought out to really compete with the low-level api.

Maybe a generated “mid-level” with proper python types and docs instead of attrdicts/json on top of the low-level jsonrpc api would already be enough.
Typescript bindings already have that because JSON is JavaScriptObjectNotation, so already typeable, python, swift and java would need more for a typed low/mid-level api, like the generated code needs to generate classes and code to parse the JSON to those classes.

alternatively, online and rendered at dc_set_config() (c.delta.chat is created from deltachat.h)

That’s where I go all the time, but the search of doxygen is nearly unusably bad IMO. that was the thing I attempted to solve with pushing forward deltachat - Rust and deltachat - Rust, but cffi.delta.chat does not have all the docs, because those are defined in the header file which is written by hand.