[ANN] pyml_bindgen: a CLI app to generate Python bindings directly from OCaml value specifications

Hi Everyone,

I wanted to announce the first release of pyml_bindgen, a CLI app for generating Python bindings using pyml directly from OCaml value specifications.

Manually writing bindings to Python libraries can get tedious pretty quickly. pyml_bindgen aims to help you avoid a lot of the repetitive work when binding Python libraries by letting you focus on the OCaml side of things and (mostly) not worrying about the implementation of the pyml bindings.

Quick start

First, install pyml_bindgen. It is available on Opam.

$ opam install pyml_bindgen

Say you have a Python class you want to bind and use in OCaml. (Filename: adder.py)

class Adder:
    @staticmethod
    def add(x, y):
        return x + y

To do so, you write OCaml value specifications for the class and methods you want to bind. (Filename: val_specs.txt)

val add : x:int -> y:int -> unit -> int

Then, you run pyml_bindgen.

$ pyml_bindgen val_specs.txt adder Adder --caml-module Adder > lib.ml

Now you can use your generated functions in your OCaml code. (Filename: run.ml)

open Lib

let () = Py.initialize ()

let result = Adder.add ~x:1 ~y:2 ()

let () = assert (result = 3)

Finally, set up a dune file and run it.

(executable
 (name run)
 (libraries pyml))
$ dune exec ./run.exe

Documentation

For more information on installing and using pyml_bindgen, check out the docs. There you will find lots of tips and examples to help you get started!

20 Likes

This looks very useful and the documentation is neat. Thank you!
I am looking forward to seeing this evolve.

2 Likes

Thanks…hopefully the docs are actually helpful as I spent a good amount of time on them!

2 Likes

I don’t know if people realize, but this Ryan Moore just opened the door to the whole python ecosystem for the OCaml world. THIS PROJECT IS AMAZINGLY USEFUL !!!
I can’t believe it.
:heart_eyes: :star_struck: :smiling_face_with_tear: :scream:

4 Likes

Thanks UnixJunkie for the kind words!

New version

I wanted to announce a new version of pyml_bindgen has been merged into the opam repository, version 0.2.0. Whenever it hits, feel free to try it out!

The main addition is now you can embed Python files directly into the generated OCaml module and it will be evaluated at run time. In this way, you don’t need your users to mess with the PYTHONPATH environment variable or need them to install a particular Python module when using the generated OCaml code. (Another thanks to UnixJunkie and Thierry Martinez for their help with this!)

There were also a few bugfixes and some nice new examples added to the GitHub repository. One cool thing about the examples is that they show you how to set up your project to use Dune rules to automatically generate Python bindings whenever the value specification files change!

6 Likes

New releases

Version 0.3.0 and 0.3.1 are now available on GitHub. 0.3.0 has been merged into opam, and a PR for 0.3.1 has been opened. The change log has more details about the changes.

Binding tuples

You can now bind tuples directly. Here’s a Python function that takes two lists of points (where each “point” is a tuple like (x, y)) and adds them together

def add(points1, points2):
    return [(x1 + y1, x2 + y2) for (x1, x2), (y1, y2) in zip(points1, points2)]

And you could bind it using tuples from the OCaml side as well.

val add : points1:(int * int) list -> points2:(int * int) list -> unit -> (int * int) list

Note there are some restrictions regarding tuples, which you can read about here, here, or here.

Attributes

You can use attributes on value specifications. Currently the only one supported is py_fun_name, which allows you to decouple the Python method name and the generated OCaml function name.

As an example, take the following Python function, which adds to “things”.

def add(x, y):
    return x + y

You could bind multiple OCaml functions to this single function now.

val add_int : x:int -> y:int -> unit -> int
[@@py_fun_name add]

val add_float : x:float -> y:float -> unit -> float
[@@py_fun_name add]

val add_string : x:string -> y:string -> unit -> string
[@@py_fun_name add]

Python magic methods

This is also nice for binding Python magic methods. For example, you don’t have to use __init__ as the name of the OCaml function you use to make instances of a Python class. You can bind it to a more natural name like create or make.

val create : name:string -> age:int -> unit -> t
[@@py_fun_name __init__]

Using Pytypes.pyobject directly

Sometimes you may not want to bother converting Python types to normal OCaml types at all. You can do that now in value specifications by using the Pytypes.pyobject and Py.Object.t types directly.

Fewer dependencies

re is now used instead of re2, which drops the number of dependencies that need to be installed by about half. Additionally, core, core_bench, and bisect_ppx don’t need to be installed if you want to install pyml_bindgen directly from the git repository, which greatly cuts the required dependencies in this case.

Thanks again to UnixJunkie for spurring many of these updates!

7 Likes

I wrote a blog post providing an introduction to pyml_bindgen. It gives an intro in a slightly different style as compared to the docs and the examples, and includes some of the latest features I’ve been working on.

Enjoy!!

6 Likes

New release

Version 0.4.1 is now available from GitHub and Opam. The change log has more details.

New stuff

New attributes

There is a new attribute you can use: py_arg_name. It allows you to use different argument names on the OCaml side from those that are used on the Python side.

One use case is for Python functions that have an argument name that is the same as some reserved OCaml keyword. In this case, you can use py_arg_name to map it to something else on the OCaml side.

val f : t -> method_:string -> unit -> string 
[@@py_arg_name method_ method]

The attribute is followed by two items, the first is the argument name on the OCaml side, and the second is the argument name on the Python side.

See the attributes example on GitHub for more info.

Helper scripts

I added a couple of scripts to help in cases where you need to run pyml_bindgen on a lot of different input files in one go. I have been using them when writing bindings for bigger Python libraries, and in cases where there are a lot of cyclic python classes to bind.

This example has more info about using the helper scripts.

Other stuff

  • Added an option to split generated modules into ml and mli files.
  • Added a dev package for (hopefully) easier installation of development dependencies.

Happy hacking!! :sparkles:

7 Likes