├── .gitattributes
├── .github
└── workflows
│ ├── build_and_test.yml
│ └── generate_docs.yml
├── .gitignore
├── .ocamlformat
├── CHANGELOG.md
├── LICENSE-APACHE.txt
├── LICENSE-MIT.txt
├── Makefile
├── README.md
├── _docs_src
├── custom_theme
│ └── main.html
├── make_docs.sh
├── mkdocs.yml
└── src
│ ├── attributes.md
│ ├── auto-gen-bindings.md
│ ├── cli-options.md
│ ├── dictionaries-2.md
│ ├── dictionaries.md
│ ├── embedding-python.md
│ ├── examples.md
│ ├── getting-started.md
│ ├── gotchas-bugs.md
│ ├── images
│ └── basic_tutorial_gitk_example.png
│ ├── img
│ ├── brown_plot.png
│ ├── orange_plot.png
│ └── silly_plot.png
│ ├── index.md
│ ├── instance-methods.md
│ ├── matplotlib-2.md
│ ├── matplotlib.md
│ ├── names.md
│ ├── ocaml-attributes.md
│ ├── of-pyobject.md
│ ├── recursive.md
│ ├── robots.txt
│ ├── rules-overview.md
│ ├── split-ml-mli.md
│ ├── static-methods.md
│ ├── todo.md
│ ├── tuples.md
│ └── types.md
├── bench
├── adder.py
├── bench_import_module.ml
├── dune
└── val_specs.txt
├── bin
├── bin_utils.ml
├── combine_rec_modules.ml
├── dune
├── gen_multi.ml
├── main_cli.ml
├── main_cli.mli
└── pyml_bindgen.ml
├── dune-project
├── examples
├── README.md
├── attributes
│ ├── README.md
│ ├── dune
│ ├── dune-project
│ ├── lib
│ │ ├── dune
│ │ ├── examples_attributes_lib.ml
│ │ └── specs
│ │ │ └── cat.txt
│ ├── py
│ │ └── cat.py
│ ├── run.ml
│ └── test
│ │ ├── dune
│ │ └── run.t
├── embedding_python_source
│ ├── README.md
│ ├── dune
│ ├── dune-project
│ ├── lib
│ │ ├── dune
│ │ ├── math.ml
│ │ └── specs
│ │ │ └── adder.txt
│ ├── py
│ │ └── math
│ │ │ └── adder.py
│ ├── run.ml
│ └── test
│ │ ├── dune
│ │ └── run.t
├── gen_ml_and_mli
│ ├── .ocamlformat
│ ├── README.md
│ ├── bin
│ │ ├── dune
│ │ └── hello.ml
│ ├── dune-project
│ ├── lib
│ │ ├── dune
│ │ ├── orange.ml
│ │ ├── orange.mli
│ │ ├── py
│ │ │ ├── orange.py
│ │ │ └── thing.py
│ │ ├── specs
│ │ │ ├── orange.txt
│ │ │ └── thing.txt
│ │ ├── thing.ml
│ │ └── thing.mli
│ └── test
│ │ ├── dune
│ │ └── it_works.t
├── importing_modules
│ ├── README.md
│ ├── dune
│ ├── dune-project
│ ├── lib
│ │ ├── dune
│ │ ├── magic_dust.ml
│ │ ├── silly_math.ml
│ │ └── specs
│ │ │ ├── magic_dust
│ │ │ ├── hearts.txt
│ │ │ └── sparkles.txt
│ │ │ └── silly_math
│ │ │ ├── adder
│ │ │ └── add.txt
│ │ │ └── subtracter
│ │ │ └── subtract.txt
│ ├── py
│ │ ├── magic_dust
│ │ │ ├── hearts.py
│ │ │ └── sparkles.py
│ │ └── silly_math
│ │ │ ├── adder
│ │ │ └── add.py
│ │ │ └── subtracter
│ │ │ └── subtract.py
│ ├── run.ml
│ └── test
│ │ ├── dune
│ │ └── run.t
├── quick_start
│ ├── .ocamlformat
│ ├── README.md
│ ├── adder.py
│ ├── dune
│ ├── dune-project
│ ├── lib.ml
│ ├── run.ml
│ ├── test
│ │ ├── dune
│ │ └── run.t
│ └── val_specs.txt
├── recursive_modules
│ ├── .ocamlformat
│ ├── README.md
│ ├── dune
│ ├── dune-project
│ ├── lib
│ │ ├── dune
│ │ ├── lib.ml
│ │ └── specs
│ │ │ ├── cat_spec.txt
│ │ │ ├── cli_specs.tsv
│ │ │ └── human_spec.txt
│ ├── py
│ │ ├── cat.py
│ │ └── human.py
│ ├── run.ml
│ └── test
│ │ ├── dune
│ │ └── run.t
└── tuples
│ ├── .ocamlformat
│ ├── README.md
│ ├── dune
│ ├── dune-project
│ ├── lib.ml
│ ├── run.ml
│ ├── test
│ ├── dune
│ └── run.t
│ ├── tuples.py
│ └── val_specs.txt
├── lib
├── dune
├── mkdir.ml
├── oarg.ml
├── otype.ml
├── py_fun.ml
├── shared.ml
├── specs_file.ml
├── utils.ml
└── version.ml
├── pyml_bindgen-dev.opam
├── pyml_bindgen.opam
├── pyml_bindgen.opam.template
├── test
├── README.md
├── basic_class_binding.t
│ ├── dune
│ ├── dune-project
│ ├── run.t
│ ├── run_no_check.ml
│ ├── run_option.ml
│ ├── run_or_error.ml
│ ├── sigs_no_check.txt
│ ├── sigs_option.txt
│ ├── sigs_or_error.txt
│ └── silly.py
├── binding_tuples.t
│ ├── dune
│ ├── lib_ml.txt
│ ├── run.ml
│ ├── run.t
│ ├── sigs.txt
│ └── silly.py
├── combine_rec_modules.t
│ ├── abc.ml
│ ├── d.ml
│ ├── e.ml
│ ├── no_modules.ml
│ ├── no_sig.ml
│ ├── one_line.ml
│ └── run.t
├── dune
├── embed_source.t
│ ├── .ocamlformat
│ ├── bad_thing.py
│ ├── dune
│ ├── dune-project
│ ├── person.py
│ ├── person2_runner.ml
│ ├── person_runner.ml
│ ├── person_specs.txt
│ ├── person_thing_runner.ml
│ ├── py
│ │ └── cool_package
│ │ │ └── person2.py
│ ├── run.t
│ ├── thing.py
│ └── thing_specs.txt
├── error_messages.t
│ ├── .ocamlformat
│ ├── run.t
│ └── val_specs.txt
├── gen_multi.t
│ ├── .ocamlformat
│ ├── bad.tsv
│ ├── run.t
│ └── specs
│ │ ├── cat_spec.txt
│ │ ├── cli_specs.tsv
│ │ ├── cli_specs_cat_first.tsv
│ │ └── human_spec.txt
├── helpers
│ └── sanitize_logs
├── module_function_binding.t
│ ├── dune
│ ├── dune-project
│ ├── run.t
│ ├── run_no_caml_module.ml
│ ├── run_with_caml_module.ml
│ ├── sigs_no_check.txt
│ └── silly.py
├── nested_submodules.t
│ ├── creature.ml
│ ├── dune
│ ├── run.t
│ ├── run_no_check.ml
│ ├── run_option.ml
│ ├── run_or_error.ml
│ ├── silly.py
│ ├── specs_no_check.txt
│ ├── specs_option.txt
│ └── specs_or_error.txt
├── of_pyo_no_check.t
│ ├── dune
│ ├── hi.ml
│ ├── run.t
│ ├── silly_mod.py
│ ├── silly_sigs_no_check.txt
│ ├── silly_sigs_no_check_option.txt
│ ├── silly_sigs_no_check_or_error.txt
│ ├── silly_sigs_option.txt
│ ├── silly_sigs_option_or_error.txt
│ └── silly_sigs_or_error.txt
├── of_pyo_option.t
│ ├── dune
│ ├── hi.ml
│ ├── run.t
│ ├── silly_mod.py
│ ├── silly_sigs_no_check.txt
│ ├── silly_sigs_no_check_option.txt
│ ├── silly_sigs_no_check_or_error.txt
│ ├── silly_sigs_option.txt
│ ├── silly_sigs_option_or_error.txt
│ └── silly_sigs_or_error.txt
├── of_pyo_or_error.t
│ ├── dune
│ ├── hi.ml
│ ├── run.t
│ ├── silly_mod.py
│ ├── silly_sigs_no_check.txt
│ ├── silly_sigs_no_check_option.txt
│ ├── silly_sigs_no_check_or_error.txt
│ ├── silly_sigs_option.txt
│ ├── silly_sigs_option_or_error.txt
│ └── silly_sigs_or_error.txt
├── old_bugs.t
│ ├── pr_value_bug.txt
│ └── run.t
├── py_fun_name_attr.t
│ ├── dune
│ ├── run.ml
│ ├── run.t
│ ├── silly.py
│ └── specs.txt
├── raw_pyobjects.t
│ ├── creature.ml
│ ├── dune
│ ├── run.ml
│ ├── run.t
│ ├── silly.py
│ └── specs.txt
├── split_caml_module.t
│ ├── .ocamlformat
│ ├── dune
│ ├── dune-project
│ ├── hello.ml
│ ├── run.t
│ ├── specs.txt
│ └── thing.py
├── test_oarg.ml
├── test_otype.ml
├── test_py_fun.ml
└── test_specs_file.ml
└── test_dev
├── README.md
├── combine_rec_modules_cli_error.t
├── dune
├── gen_multi_cli_error.t
├── bad.tsv
├── run.t
└── specs
│ ├── bad
│ └── bad.tsv
│ └── bad_file_names.tsv
└── pyml_bindgen_cli_error.t
├── run.t
└── specs.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | _docs_src -linguist-detectable
2 | test/*/run.t -linguist-detectable
--------------------------------------------------------------------------------
/.github/workflows/build_and_test.yml:
--------------------------------------------------------------------------------
1 | name: Build and test
2 |
3 | on:
4 | push:
5 | branches:
6 | - "main"
7 | - "staging"
8 | - "dev"
9 |
10 | pull_request:
11 | branches:
12 | - "*"
13 |
14 | jobs:
15 | build:
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | os:
20 | - ubuntu-latest
21 | ocaml-compiler:
22 | - 4.14.0
23 | - 4.08.1
24 |
25 | runs-on: ${{ matrix.os }}
26 |
27 | steps:
28 | - name: Checkout code
29 | uses: actions/checkout@v2
30 |
31 | - name: Use OCaml ${{ matrix.ocaml-compiler }}
32 | uses: ocaml/setup-ocaml@v2
33 | with:
34 | ocaml-compiler: ${{ matrix.ocaml-compiler }}
35 | dune-cache: true
36 | cache-prefix: v1-${{ matrix.os }}
37 | opam-local-packages: pyml_bindgen.opam
38 |
39 | - run: opam exec -- make deps
40 |
41 | - name: Check release profile
42 | run: |
43 | opam exec -- make clean
44 | opam exec -- make build_release
45 | opam exec -- make test_release
46 | opam exec -- make install_release
47 |
48 | - name: Check dev profile
49 | run: |
50 | opam exec -- make deps_dev
51 | opam exec -- make clean
52 | opam exec -- make build_dev
53 | opam exec -- make test_dev
54 | opam exec -- make install_dev
55 | if: matrix.ocaml-compiler == '4.14.0'
56 |
57 | # Send the coverage.
58 | - name: Send coverage
59 | run: |
60 | opam install bisect_ppx
61 | opam exec -- make send_coverage
62 | if: matrix.ocaml-compiler == '4.14.0'
63 | env:
64 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65 |
--------------------------------------------------------------------------------
/.github/workflows/generate_docs.yml:
--------------------------------------------------------------------------------
1 | name: Generate Docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 |
8 | jobs:
9 | docs:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0
17 | submodules: recursive
18 |
19 | - run: pip install mkdocs
20 | - run: cd _docs_src && ./make_docs.sh
21 | - run: git checkout gh-pages
22 | - run: if [ -d docs ]; then rm -r docs; fi
23 | - run: mv _docs docs
24 |
25 | - name: Commit site changes
26 | run: |
27 | git config --global user.name github-actions
28 | git config --global user.email github-actions@github.com
29 | git add docs
30 | git commit -m "Update docs site"
31 | git push
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore all dirs starting with _
2 | _*
3 |
4 | # Un-ignore some of them
5 | !_docs_src
6 |
--------------------------------------------------------------------------------
/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Unreleased
2 |
3 | ## 0.4.1 (2022-08-01)
4 |
5 | ### Fixed
6 |
7 | - Fixed cram dependency specification
8 |
9 | ## 0.4.0 (2022-07-31)
10 |
11 | ### Added
12 |
13 | - Add helper scripts for binding cyclic Python classes
14 | - `gen_multi` is a wrapper for `pyml_bindgen` that takes a TSV file of command line specs, and runs `pyml_bindgen` on each of them.
15 | - `combine_rec_modules` is a small program that takes generated OCaml modules and "converts" them into recursive modules
16 | - You can combine these two to make it easier to generate recursive modules, which can be useful for binding Python classes that reference each other.
17 | - Add support for `py_arg_name` attribute. It lets you use different argument names in the OCaml bindings than used in the Python functions.
18 | - Add `--split-caml-module` option to split generated module into separate `ml` and `mli` files.
19 | - Add `pyml_bindgen-dev.opam` for easier installation of development dependencies.
20 |
21 | ## 0.3.1 (2022-03-23)
22 |
23 | ### Changed
24 |
25 | - Reduce number of dependencies (including when installing from the GitHub repository).
26 | - Use `re` instead of `re2` for regular expressions.
27 | - Drop some of the dev dependencies from the `opam` file.
28 |
29 | ## 0.3.0 (2022-03-18)
30 |
31 | ### Added
32 |
33 | - Allow nested module types in val specs (e.g., `Food.Dessert.Apple_pie.t`)
34 | - Allow using `Pytypes.pyobject` and `Py.Object.t` in val specs
35 | - Better error messages when parser or `py_fun` creation fails
36 | - You can now use attributes on value specifications.
37 | - Currently the only one available is `py_fun_name`.
38 | - It allows you to decouple the Python method name and the generated OCaml function name.
39 | - See the examples directory on GitHub for more info.
40 | - You can now bind tuples with 2, 3, 4, or 5 elements.
41 | - They can be passed in as arguments, or returned from functions.
42 | - Only basic types and Python objects are allowed in tuples.
43 | - You can also put tuples inside of collections, e.g., `(int * string) list`, but not Options or Or_errors.
44 |
45 | ### Changed
46 |
47 | - Updated docs
48 | - Update to dune 3
49 | - Update to cmdliner 1.1
50 |
51 | ### Fixed
52 |
53 | - Fix some small `otype` bugs
54 |
55 | ## 0.2.0 (2022-02-02)
56 |
57 | - Allow embedding Python source directly into generated OCaml module with the `--embed-python-source` CLI option. See this [issue](https://github.com/mooreryan/ocaml_python_bindgen/issues/5) for more info.
58 | - Fix bug in val spec parsing
59 | - Update docs
60 | - Add full examples in the `examples` directory
61 |
62 | ## 0.1.2 (2021-12-07)
63 |
64 | - Use specific `ocamlformat` version for the tests. See this Opam repository [pull request](https://github.com/ocaml/opam-repository/pull/20162#issuecomment-987010684) for more info.
65 |
66 | ## 0.1.1 (2021-11-04)
67 |
68 | - Update lower bounds for dependencies
69 | - Fix tests to work with BusyBox/Alpine `grep` command
70 |
71 | ## 0.1.0 (2021-10-31)
72 |
73 | Initial release!
74 |
--------------------------------------------------------------------------------
/LICENSE-MIT.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ryan M. Moore
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BROWSER = firefox
2 | TEST_COV_D = /tmp/pyml_bindgen
3 |
4 | .PHONY: all_dev
5 | all_dev: clean build_dev test_dev
6 |
7 | .PHONY: all_release
8 | all_release: clean build_release test_release
9 |
10 | .PHONY: build_dev
11 | build_dev:
12 | dune build --profile=dev
13 |
14 | .PHONY: build_release
15 | build_release:
16 | dune build --profile=release
17 |
18 | .PHONY: check
19 | check:
20 | dune build @check
21 |
22 | .PHONY: clean
23 | clean:
24 | dune clean
25 |
26 | .PHONY: deps
27 | deps:
28 | opam install ./pyml_bindgen.opam --deps-only --with-doc --with-test
29 |
30 | .PHONY: deps_dev
31 | deps_dev:
32 | opam install ./pyml_bindgen-dev.opam --deps-only
33 |
34 | .PHONY: install_dev
35 | install_dev:
36 | dune install --profile=dev
37 |
38 | .PHONY: install_release
39 | install_release:
40 | dune install --profile=release
41 |
42 | .PHONY: opam_install
43 | opam_install:
44 | opam install ./pyml_bindgen.opam
45 |
46 | .PHONY: promote
47 | promote:
48 | dune promote
49 |
50 | .PHONY: uninstall
51 | uninstall:
52 | dune uninstall
53 |
54 | .PHONY: test_dev
55 | test_dev:
56 | dune runtest --profile=dev
57 |
58 | .PHONY: test_release
59 | test_release:
60 | dune runtest --profile=release
61 |
62 | .PHONY: test_coverage
63 | test_coverage:
64 | if [ -d $(TEST_COV_D) ]; then rm -r $(TEST_COV_D); fi
65 | mkdir -p $(TEST_COV_D)
66 | BISECT_FILE=$(TEST_COV_D)/pyml_bindgen dune runtest --no-print-directory \
67 | --instrument-with bisect_ppx --force
68 | bisect-ppx-report html --coverage-path $(TEST_COV_D)
69 | bisect-ppx-report summary --coverage-path $(TEST_COV_D)
70 |
71 | .PHONY: test_coverage_open
72 | test_coverage_open: test_coverage
73 | $(BROWSER) _coverage/index.html
74 |
75 | .PHONY: send_coverage
76 | send_coverage: test_coverage
77 | bisect-ppx-report send-to Coveralls --coverage-path $(TEST_COV_D)
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OCaml-Python Bindings Generator
2 |
3 | [](https://github.com/mooreryan/ocaml_python_bindgen/actions/workflows/build_and_test.yml) [](https://coveralls.io/github/mooreryan/ocaml_python_bindgen?branch=main)
4 |
5 | Generate Python bindings with [pyml](https://github.com/thierry-martinez/pyml) directly from OCaml value specifications.
6 |
7 | While you _could_ write all your Python bindings by hand, it can be tedious and it gets old real quick. While `pyml_bindgen` can't yet auto-generate all the bindings you may need, it can definitely take care of a lot of the tedious and repetitive work you need to do when writing bindings for a big Python library!! 💖
8 |
9 | ## Quick start
10 |
11 | First, install `pyml_bindgen`. It is available on [Opam](https://opam.ocaml.org/packages/pyml_bindgen/).
12 |
13 | ```
14 | $ opam install pyml_bindgen
15 | ```
16 |
17 | Say you have a Python class you want to bind and use in OCaml. (Filename: `adder.py`)
18 |
19 | ```python
20 | class Adder:
21 | @staticmethod
22 | def add(x, y):
23 | return x + y
24 | ```
25 |
26 | To do so, you write OCaml value specifications for the class and methods you want to bind. (Filename: `val_specs.txt`)
27 |
28 | ```ocaml
29 | val add : x:int -> y:int -> unit -> int
30 | ```
31 |
32 | Then, you run `pyml_bindgen`.
33 |
34 | ```
35 | $ pyml_bindgen val_specs.txt adder Adder --caml-module Adder > lib.ml
36 | ```
37 |
38 | Now you can use your generated functions in your OCaml code. (Filename: `run.ml`)
39 |
40 | ```ocaml
41 | open Lib
42 |
43 | let () = Py.initialize ()
44 |
45 | let result = Adder.add ~x:1 ~y:2 ()
46 |
47 | let () = assert (result = 3)
48 | ```
49 |
50 | Finally, set up a dune file and run it.
51 |
52 | ```
53 | (executable
54 | (name run)
55 | (libraries pyml))
56 | ```
57 |
58 | ```
59 | $ dune exec ./run.exe
60 | ```
61 |
62 | ## Documentation
63 |
64 | For information on installing and using `pyml_bindgen`, check out the [docs](https://mooreryan.github.io/ocaml_python_bindgen/).
65 |
66 | Additionally, you can find examples in the [examples](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples) directory. One neat thing about these examples is that you can see how to write Dune [rules](https://dune.readthedocs.io/en/stable/dune-files.html#rule) to automatically generate your `pyml` bindings.
67 |
68 | You may also want to check out my [blog post](https://www.tenderisthebyte.com/blog/2022/04/12/ocaml-python-bindgen/) introducing `pyml_bindgen`.
69 |
70 | ## Installing from sources
71 |
72 | If you want to install from sources, e.g., to track the main branch or a development branch, but you do not want to install all the test and development packages, clone the repository, checkout the branch you want to follow and install:
73 |
74 | ```
75 | $ git clone https://github.com/mooreryan/ocaml_python_bindgen.git
76 | # Checkout whatever branch you want, in this case `dev`.
77 | $ git checkout dev
78 | $ make opam_install
79 | ```
80 |
81 | This will save a lot of install time as it avoids some heavy packages.
82 |
83 | ## Development
84 |
85 | If instead, you want to work on `pyml_bindgen` development, will need to ensure you have the development and test dependencies.
86 |
87 | E.g.,
88 |
89 | ```
90 | $ git clone https://github.com/mooreryan/ocaml_python_bindgen.git
91 | $ make deps
92 | $ make deps_dev
93 | # Start working!
94 | $ dune test
95 | ```
96 |
97 | ## License
98 |
99 | [](https://github.com/mooreryan/pasv)
101 |
102 | Copyright (c) 2021 - 2022 Ryan M. Moore.
103 |
104 | Licensed under the Apache License, Version 2.0 or the MIT license, at your option. This program may not be copied, modified, or distributed except according to those terms.
105 |
--------------------------------------------------------------------------------
/_docs_src/custom_theme/main.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block extrahead %}
4 | {% set title = config.site_name %}
5 |
6 | {% if page and page.title and not page.is_homepage %}
7 | {% set title = page.title ~ " - " ~ config.site_name | striptags %}
8 | {% endif %}
9 |
10 | {% set description = config.site_description %}
11 |
12 | {% if page and page.meta.description %}
13 | {% set description = page.meta.description %}
14 | {% endif %}
15 |
16 | {% if page and page.canonical_url %}
17 | {% set canonical_url = page.canonical_url %}
18 | {% endif %}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {% if page and page.meta.image %}
39 | {% set image = config.site_url ~ page.meta.image %}
40 |
41 |
42 | {% endif %}
43 |
44 | {% if page and page.meta and page.meta.main %}
45 |
46 | {% endif%}
47 |
48 | {% endblock %}
49 |
--------------------------------------------------------------------------------
/_docs_src/make_docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | time mkdocs build
4 |
--------------------------------------------------------------------------------
/_docs_src/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: pyml_bindgen -- OCaml-Python Bindings Generator
2 | site_url: https://mooreryan.github.io/ocaml_python_bindgen/
3 | repo_url: https://github.com/mooreryan/ocaml_python_bindgen
4 | edit_uri: edit/main/_docs_src/src
5 | site_description: Installation and usage instructions for pyml_bindgen, a program to generate OCaml bindings for Python classes via pyml.
6 | site_author: Ryan M. Moore
7 | nav:
8 | - pyml_bindgen: index.md
9 | - Examples & Tutorials:
10 | - Getting Started: getting-started.md
11 | - Lots of examples: examples.md
12 | - Matplotlib Example: matplotlib.md
13 | - Matplotlib Example 2: matplotlib-2.md
14 | - Handling Dictionaries: dictionaries.md
15 | - Handling Dictionaries 2: dictionaries-2.md
16 | - Recursive Classes: recursive.md
17 | - Rules:
18 | - Overview: rules-overview.md
19 | - Types: types.md
20 | - Function & Argument Names: names.md
21 | - Attributes & Properties: attributes.md
22 | - Instance Methods: instance-methods.md
23 | - Class & Static Methods; Functions: static-methods.md
24 | - Miscellaneous:
25 | - Auto-generating bindings: auto-gen-bindings.md
26 | - Attributes -- py_fun_name: ocaml-attributes.md
27 | - Converting `pyobjects` to OCaml types: of-pyobject.md
28 | - Embedding Python source: embedding-python.md
29 | - Handling Tuples: tuples.md
30 | - Gotchas & Known Bugs: gotchas-bugs.md
31 | - Splitting ml and mli files: split-ml-mli.md
32 | theme:
33 | name: readthedocs
34 | custom_dir: custom_theme
35 | highlightjs: true
36 | hljs_languages:
37 | - ocaml
38 | - python
39 | markdown_extensions:
40 | - meta
41 | docs_dir: src
42 | site_dir: ../_docs
43 |
--------------------------------------------------------------------------------
/_docs_src/src/attributes.md:
--------------------------------------------------------------------------------
1 | # Attributes & Properties
2 |
3 | Value specifications that take a single argument `t` will be interpreted as bindings to Python attributes or properties.
4 |
5 | Value specs for attributes and properties look like this:
6 |
7 | ```ocaml
8 | val f : t -> 'a
9 | ```
10 |
11 | ## Rules
12 |
13 | * The first and only function argument must be `t`.
14 | * The return type can be any of the [types](./types.md) mentioned earlier.
15 |
16 | ## Examples
17 |
18 | ```ocaml
19 | val x : t -> int
20 | val name : t -> string
21 | val price : t -> float
22 | ```
23 |
--------------------------------------------------------------------------------
/_docs_src/src/auto-gen-bindings.md:
--------------------------------------------------------------------------------
1 | # Automatically Generating Bindings
2 |
3 | Sometimes it can be a bit of a pain to always have to run `pyml_bindgen` by hand.
4 |
5 | One way to avoid this is by adding a [rule](https://dune.readthedocs.io/en/stable/dune-files.html#rule) to your Dune file.
6 |
7 | For an example of this, check out the `dune` file in the [quick start](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/quick_start) example on GitHub. It has lots of comments to show you how it is working.
8 |
--------------------------------------------------------------------------------
/_docs_src/src/cli-options.md:
--------------------------------------------------------------------------------
1 | # CLI Options
2 |
3 | For reference, here are the CLI opts:
4 |
5 | ```text
6 | $ pyml_bindgen --help
7 | NAME
8 | pyml_bindgen - generate pyml bindings for a set of signatures
9 |
10 | SYNOPSIS
11 | pyml_bindgen [OPTION]... SIGNATURES PY_MODULE PY_CLASS
12 |
13 | DESCRIPTION
14 | Generate pyml bindings from OCaml signatures.
15 |
16 | ARGUMENTS
17 | PY_CLASS (required)
18 | Python class name
19 |
20 | PY_MODULE (required)
21 | Python module name
22 |
23 | SIGNATURES (required)
24 | Path to signatures
25 |
26 | OPTIONS
27 | -a ASSOCIATED_WITH, --associated-with=ASSOCIATED_WITH (absent=class)
28 | Are the Python functions associated with a class or just a module?
29 | ASSOCIATED_WITH must be either `class' or `module'.
30 |
31 | -c CAML_MODULE, --caml-module=CAML_MODULE
32 | Write full module and signature
33 |
34 | --help[=FMT] (default=auto)
35 | Show this help in format FMT. The value FMT must be one of `auto',
36 | `pager', `groff' or `plain'. With `auto', the format is `pager` or
37 | `plain' whenever the TERM env var is `dumb' or undefined.
38 |
39 | -r OF_PYO_RET_TYPE, --of-pyo-ret-type=OF_PYO_RET_TYPE (absent=option)
40 | Return type of the of_pyobject function. OF_PYO_RET_TYPE must be
41 | one of `no_check', `option' or `or_error'.
42 |
43 | --version
44 | Show version information.
45 |
46 | BUGS
47 | Please report any bugs or issues on GitHub.
48 | (https://github.com/mooreryan/pyml_bindgen/issues)
49 |
50 | SEE ALSO
51 | For full documentation, please see the GitHub page.
52 | (https://github.com/mooreryan/pyml_bindgen)
53 |
54 | AUTHORS
55 | Ryan M. Moore
56 |
57 | ```
58 |
--------------------------------------------------------------------------------
/_docs_src/src/embedding-python.md:
--------------------------------------------------------------------------------
1 | # Embedding Python Source Code
2 |
3 | If you import modules in Python code, Python needs to be able to actually find these modules.
4 |
5 | One way is to "install" the Python module you want with say, pip. E.g., `pip install whatever`.
6 |
7 | If you're working on a Python module along with your OCaml code, you probably don't want to do this. Rather, you probably want to set the `PYTHONPATH` environment variable to the location of your Python modules. (You can find an example an detailed explanation of that in the [importing modules](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/importing_modules) example on GitHub.)
8 |
9 | The third option is to embed the Python code directly into the generated OCaml module. The `--embed-python-source` CLI option lets you do this. Basically, you just provide the path to the Python module you want to embed to that option, and it will all work out for you. This way you don't have to have your user's worry about setting the `PYTHONPATH` properly.
10 |
11 | To see it in action, check out the [example](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/embedding_python_source) on GitHub.
12 |
13 |
14 |
--------------------------------------------------------------------------------
/_docs_src/src/examples.md:
--------------------------------------------------------------------------------
1 | # Lots of Examples
2 |
3 | You can find a lot of examples on [GitHub](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples).
4 |
5 | These examples are particularly cool because they show you how to use Dune [rules](TODO) to generate bindings automatically. Also, they are tested along with the rest of the `pyml_bindgen` code, so they are guaranteed to be up-to-date and working.
6 |
--------------------------------------------------------------------------------
/_docs_src/src/gotchas-bugs.md:
--------------------------------------------------------------------------------
1 | # Gotchas & Known Bugs
2 |
3 | * You cannot bind Python properties or attributes that return `None`. So, `val f : t -> unit` will currently fail.
4 |
--------------------------------------------------------------------------------
/_docs_src/src/images/basic_tutorial_gitk_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooreryan/ocaml_python_bindgen/e17762a03d8726ecdb92f3e31ae9f6ab3687e8c5/_docs_src/src/images/basic_tutorial_gitk_example.png
--------------------------------------------------------------------------------
/_docs_src/src/img/brown_plot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooreryan/ocaml_python_bindgen/e17762a03d8726ecdb92f3e31ae9f6ab3687e8c5/_docs_src/src/img/brown_plot.png
--------------------------------------------------------------------------------
/_docs_src/src/img/orange_plot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooreryan/ocaml_python_bindgen/e17762a03d8726ecdb92f3e31ae9f6ab3687e8c5/_docs_src/src/img/orange_plot.png
--------------------------------------------------------------------------------
/_docs_src/src/img/silly_plot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooreryan/ocaml_python_bindgen/e17762a03d8726ecdb92f3e31ae9f6ab3687e8c5/_docs_src/src/img/silly_plot.png
--------------------------------------------------------------------------------
/_docs_src/src/instance-methods.md:
--------------------------------------------------------------------------------
1 | # Instance Methods
2 |
3 | Value specs for instance methods look like this:
4 |
5 | ```ocaml
6 | val f : t -> a:'a -> ?b:'b -> ... -> unit -> 'c
7 | ```
8 |
9 | ## Rules
10 |
11 | * The first argument must be `t`.
12 | * The final function argument (penultimate type expression) must be `unit`.
13 | * The return type can be any of the [types](./types.md) mentioned earlier.
14 | * The remaining function arguments must either be named or optional. The types of these arguments can be any of the types mentioned earlier.
15 |
16 | Note on the final unit argument...I require all arguments that bind to Python method arguments be named or optional. Python will often have optional named arguments at the end of a function's arguments. In OCaml, these can't be erased unless you have a unit argument that comes after. So, to keep the APIs all looking similar, I decided that all instance and static methods would end in a final unit argument. This may change in the future.
17 |
--------------------------------------------------------------------------------
/_docs_src/src/names.md:
--------------------------------------------------------------------------------
1 | # Function & Argument Names
2 |
3 | You can't pick just any old name for your functions and arguments :)
4 |
5 | The main thing to remember is in addition to being valid OCaml names, they must also be [valid python names](https://docs.python.org/3/reference/lexical_analysis.html#identifiers). This is because we pass the function name and argument names "as-is" to Python.
6 |
7 |
8 | In addition to that, there are a couple other things to keep in mind.
9 |
10 | * Argument names that match any of the [types](types.md) mentioned previously are not allowed.
11 | * Argument names that start with any of the types mentioned are not allowed. (E.g., `val foo : t -> int_thing:string -> unit -> float` will fail.)
12 | * Argument names that end with any of the above types are actually okay. You probably shouldn't name them like this but it works. Really, it's just an artifact of the parsing :)
13 | * Function names and arguments can start with underscores (e.g., `__init__`) but they cannot be *all* underscores. E.g., `val ____ : ...` will not parse.
14 |
15 | ## Reserved keywords
16 |
17 | Sometimes, you are binding a Python method that uses reserved OCaml identifiers either in the name of the method names of the arguments.
18 |
19 | You can get around this using [attributes](./ocaml-attributes.md) in your value specifications.
20 |
21 | ```ocaml
22 | val downto_ : from:int -> to_:int -> unit -> int array
23 | [@@py_fun_name downto]
24 | [@@py_arg_name to_ to]
25 | [@@py_arg_name from from_]
26 | ```
27 |
28 | (Here is the Python you're binding in this case.)
29 |
30 | ```python
31 | def downto(from_, to):
32 | ...
33 | ```
34 |
--------------------------------------------------------------------------------
/_docs_src/src/ocaml-attributes.md:
--------------------------------------------------------------------------------
1 | # Attributes
2 |
3 | You can attach certain attributes to your value specifications to change their behavior.
4 |
5 | Currently, the only recognized attributes are `py_fun_name` and `py_arg_name`.
6 |
7 | You can use them to bind a Python method names and argument names to something else on the OCaml side.
8 |
9 | ## Example
10 |
11 | For example, rather than having a `Cat.__init__` method to call from the OCaml side (i.e., to make a new Python `Cat` object), you can bind the Python `__init__` function to something more idiomatic like `create` or whatever you want.
12 |
13 | ```ocaml
14 | val create : t -> ...
15 | [@@py_fun_name __init__]
16 | ```
17 |
18 | Another common use for this is to bind the Python `__str__` method for a class to `to_string` on the OCaml side.
19 |
20 | You can do this with any function. One reason is that you may want to have some type safety with a polymorphic Python function. While you could pass in [Py.Object.t](https://mooreryan.github.io/ocaml_python_bindgen/types/#pytypes) directly, you could also use attributes to bind multiple OCaml functions to the same Python method. E.g.,
21 |
22 | ```ocaml
23 | val eat : t -> num_mice:int -> unit -> unit
24 |
25 | val eat_part : t -> num_mice:float -> unit -> unit
26 | [@@py_fun_name eat]
27 | ```
28 |
29 | In this case, we have one `eat` function for `int` and one for `float`.
30 |
31 | ## Full example
32 |
33 | For a full working example see the [attributes](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/attributes) example on GitHub.
34 |
35 | The linked example also shows how to use `py_arg_name`.
36 |
37 | ## Warning
38 |
39 | - If you specify the `py_fun_name` more than once, it will do something wonky. Eventually, the program will treat this as an error, but for now, it is on you to avoid doing it.
40 | - Attributes have to start a line. I.e., if you have more than one attribute, you can't put them on the same line. They must go on separate lines.
41 |
--------------------------------------------------------------------------------
/_docs_src/src/of-pyobject.md:
--------------------------------------------------------------------------------
1 | # Converting `pyobjects` to OCaml Types
2 |
3 | With `pyml_bindgen`, you are generally want to set up a binding from a single Python class to a single OCaml module.
4 |
5 | E.g.,
6 |
7 | ```python
8 | class Foo:
9 | def __init__(self, x)
10 | self.x = x
11 |
12 | def add1(self):
13 | self.x += 1
14 | ```
15 |
16 | would have a corresponding module something like this
17 |
18 | ```ocaml
19 | module Foo : sig
20 | type t
21 |
22 | val of_pyobject : Pytypes.pyobject -> t
23 | val to_pyobject : t -> Pytypes.pyobject
24 |
25 | val x : t -> int
26 |
27 | val add1 : t -> unit -> unit
28 | end = struct
29 | type t = Pytypes.pyobject
30 |
31 | let of_pyobject x = x
32 | let to_pyobject x = x
33 |
34 | let add1 t () =
35 | end
36 | ```
37 |
38 | In the above example, we don't bother checking the Python-land type of the `pyobject`. All OCaml compiler knows at compile time is that we are taking a `Pytypes.pyobject` type and getting back a `Foo.t`.
39 |
40 | Depending on how that `pyobject` was actually created elsewhere in the code, it might not actually be an instance of the `Foo` class. In this case, when you go to call the `Foo.say_hi` function in your OCaml code, you will get a runtime error. Let me give you an example.
41 |
42 | ```ocaml
43 | let i = Py.Int.of_int 1
44 | assert
45 | let foo = Foo.of_pyobject i
46 |
47 | let _ = Foo.say_hi foo ()
48 | (* ERROR! *)
49 | ```
50 |
51 | You'll get an exception: `Exception: E (, 'int' object has no attribute 'add1')`.
52 |
53 | ## Checking `pyobjects` at module boundaries
54 |
55 |
56 | While you *could* remove the `of_pyobject` function from the interface, you are often going to need it outside the module. For example, you may have a Python class `Foo` that has a method which returns an object of class `Bar`. In your OCaml code you'd need to call the `Bar.of_pyobject` method from inside the `Foo` module.
57 |
58 | Basically, you would like to have an `of_pyobject` that actually checks that the underlying Python type is what the module expects. I.e., you only want to create a `Foo.t` if the `pyobject` is a `Foo` object in Python-land.
59 |
60 | You can address this problem in the typical OCaml way (e.g., by returning `t option` or `t Or_error.t` instead of `t`) in `pyml_bindgen` as well. Let's see what I mean.
61 |
62 | `pyml_bindgen` automatically generates `of_pyobject` and `to_pyobject` functions for you (in fact, you shouldn't provide those yourself).
63 |
64 | You can generate three kinds `of_pyobject` function with `pyml_bindgen`:
65 |
66 | * No checking: `val of_pyobject : Pytypes.pyobject -> t`
67 | * `option` returning: ` val of_pyobject : Pytypes.pyobject -> t option`
68 | * [Base](https://ocaml.janestreet.com/ocaml-core/latest/doc/base/Base/Or_error/index.html) `Or_error.t` returning: ` val of_pyobject : Pytypes.pyobject -> t Or_error.t`
69 |
70 | You can choose between the three with the `--of-pyo-ret-type` option. Here is the section from the man page:
71 |
72 | ```
73 | -r OF_PYO_RET_TYPE, --of-pyo-ret-type=OF_PYO_RET_TYPE (absent=option)
74 | Return type of the of_pyobject function. OF_PYO_RET_TYPE must be
75 | one of `no_check', `option' or `or_error'.
76 | ```
77 |
78 | While the `option` and `Or_error.t` let you avoid a lot of potential runtime problems, they will force you to deal with potential errors each time `of_pyobject` is called, and in code generated by `pyml_bindgen` you may not realize that it is being called!
79 |
80 | Say that you generated both `Person` and `Job` modules with the `--of-pyo-ret-type=option` command line option. Then both of these modules will have `of_pyobject` functions that return `t option` rather than just `t`.
81 |
82 | *Note: For now, you can only generate one of these module signatures at a time with `pyml_bindgen`. To combine them, you'll have to run it multiple times and then combine manually.*
83 |
84 | Here is an example of code that won't work.
85 |
86 | ```ocaml
87 | module rec Person : sig
88 | type t
89 | val of_pyobject : Pytypes.pyobject -> t option
90 | (* Oops! *)
91 | val get_job : t -> unit -> Job.t
92 | ...
93 | end = struct ... end
94 |
95 | and Job : sig
96 | type t
97 | val of_pyobject : Pytypes.pyobject -> t option
98 | ...
99 | end = struct ... end
100 | ```
101 |
102 | When `pyml_bindgen` sees a function that ends in a custom type (a module type like `Job.t`, `Person.t`, or whatever), the generated code will call that type's `of_pyobject` function to convert it to the correct OCaml type. So, for `Person.get_job` it will generate a function that calls `Job.of_pyobject` somewhere in the `get_job` implementation. Of course, `Job.of_pyobject` returns `Job.t option` and not `Job.t`. But in the `Person.get_job` signature, we've specified that `get_job` returns `Job.t` and *NOT* `Job.t option`.
103 |
104 | Now, `pyml_bindgen` will happily generate this implementation for you, but when you try to actually compile it, you will get an error about the return type of `get_job` implementation not matching the expected signature.
105 |
106 | So what do you do? Well, you have to remember that the `--of-pyo-ret-type=option` and `--of-pyo-ret-type=or_error` flags will essentially poison all generated functions that manipulate other auto-generated modules.
107 |
108 | Specifically, for this example, you can't write `val get_job : t -> unit -> Job.t`. Instead, you have to write `val get_job : t -> unit -> Job.t option`. Just so that it's clear, the reason is because `Job.of_pyobject` returns `Job.t option`, and the generated implementation of `Person.get_job` will call `Job.of_pyobject` somewhere in its body.)
109 |
110 | ## Wrap-up
111 |
112 | You have to be aware of the return types of the `of_pyobject` functions you're generating with `pyml_bindgen`. If you use `option` or `Or_error.t`, you have to remember to adjust your value specifications accordingly!
113 |
--------------------------------------------------------------------------------
/_docs_src/src/recursive.md:
--------------------------------------------------------------------------------
1 | # Binding Recursive Classes
2 |
3 | You will often run into cases in which you need to bind classes that are cyclical. Here's an example:
4 |
5 | ```python
6 | class Foo:
7 | @staticmethod
8 | def make_bar():
9 | return Bar()
10 |
11 | class Bar:
12 | @staticmethod
13 | def make_foo():
14 | return Foo()
15 | ```
16 |
17 | `Foo` has a method that returns a `Bar` object, and `Bar` has a method that returns a `Foo` object.
18 |
19 | While this works fine in Python, we have to be more explicit in OCaml in these kinds of situations.
20 |
21 | ## Auto-generate bindings
22 |
23 | As of version `0.4.0-SNAPSHOT`, `pyml_bindgen` ships two helper scripts for dealing with this type of thing automatically: `gen_multi` and `combine_rec_modules`. Check out the [Recursive Modules](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/recursive_modules) example on GitHub for how to use them.
24 |
25 | ## Semi-manually generate bindings
26 |
27 | The `pyml_bindgen` itself doesn't handle recursive modules. But it is simple enough to edit the output by hand. Let's see how.
28 |
29 | ### Value specs
30 |
31 | Since there are two classes to bind, we will make two val spec files.
32 |
33 | `foo_val_specs.txt`
34 |
35 | ```ocaml
36 | val make_bar : unit -> Bar.t
37 | ```
38 |
39 | `bar_val_specs.txt`
40 |
41 | ```ocaml
42 | val make_foo : unit -> Foo.t
43 | ```
44 |
45 | ### Run `pyml_bindgen`
46 |
47 | Now, run `pyml_bindgen` with some extra shell commands to make the output look nicer.
48 |
49 | ```
50 | pyml_bindgen foo_val_specs.txt silly Foo --caml-module Foo -r no_check \
51 | | ocamlformat --enable --name=a.ml - > lib.ml
52 |
53 | printf "\n" >> lib.ml
54 |
55 | pyml_bindgen bar_val_specs.txt silly Bar --caml-module Bar -r no_check \
56 | | ocamlformat --enable --name=a.ml - >> lib.ml
57 | ```
58 |
59 | ### Fix the output
60 |
61 | If you were to try and compile that code, you'd get a lot of errors including about unknown`Bar` module.
62 |
63 | To fix it, change `module Foo : sig` to `module rec Foo : sig` and `module Bar : sig` to `and Bar : sig`.
64 |
65 | Once you do that, everything will compile fine :)
66 |
67 | Here is what the output should look like:
68 |
69 | ```ocaml
70 | module rec Foo : sig
71 |
72 | ... sig ...
73 |
74 | end = struct
75 |
76 | ... impl ...
77 |
78 | end
79 |
80 | and Bar : sig
81 |
82 | ... sig ...
83 |
84 | end = struct
85 |
86 | ... impl ...
87 |
88 | end
89 | ```
90 |
91 | ### Using the generated modules
92 |
93 | You can use the generated modules as you would any others.
94 |
95 | ```ocaml
96 | open Lib
97 |
98 | let () = Py.initialize ()
99 |
100 | let (_bar : Bar.t) = Foo.make_bar ()
101 | let (_foo : Foo.t) = Bar.make_foo ()
102 | ```
103 |
104 | ## Wrap-up
105 |
106 | You may come across cyclic classes when binding Python code. If you want to bind them in OCaml as it, you will need to use recursive module. This page shows you how to do it semi-manually using `pyml_bindgen`. If you would like a more automatic way to do this, see the [Recursive Modules](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/recursive_modules) example on GitHub.
107 |
--------------------------------------------------------------------------------
/_docs_src/src/robots.txt:
--------------------------------------------------------------------------------
1 | Sitemap: https://mooreryan.github.io/ocaml_python_bindgen/sitemap.xml
2 |
--------------------------------------------------------------------------------
/_docs_src/src/rules-overview.md:
--------------------------------------------------------------------------------
1 | # Rules Overview
2 |
3 | The following pages are an informal description of some of the rules you have to follow when using `pyml_bindgen`.
4 |
5 | I will try and keep them updated and (reasonably) complete. However, if you really want to know how something is working, you may want to have a look at the [tests](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/test) or the [examples](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples).
6 |
--------------------------------------------------------------------------------
/_docs_src/src/split-ml-mli.md:
--------------------------------------------------------------------------------
1 | # Splitting generated modules
2 |
3 | You can split the generated module into separate `ml` and `mli` files using the `--split-caml-module` option.
4 |
5 | For a full example of this, see the [example](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/gen_ml_and_mli) on GitHub.
6 |
--------------------------------------------------------------------------------
/_docs_src/src/static-methods.md:
--------------------------------------------------------------------------------
1 | # Class & Static Methods; Functions
2 |
3 | Value specs for class/static methods look like this:
4 |
5 | ```ocaml
6 | val f : a:'a -> ?b:'b -> ... -> unit -> 'c
7 | ```
8 |
9 | ## Rules
10 |
11 | * The final function argument (penultimate type expression) must be `unit`.
12 | * The return type can be any of the [types](./types.md) mentioned earlier.
13 | * The remaining function arguments must either be named or optional. The types of these arguments can be any of the types mentioned earlier.
14 |
15 | ## Examples
16 |
17 | ```ocaml
18 | val add_item : fruit:string -> price:float -> unit -> unit
19 | val subtract : x:int -> ?y:int -> unit -> int
20 | ```
21 |
22 | ## Binding `__init__`
23 |
24 | `__init__` methods are called when constructing new Python objects. Here is an example.
25 |
26 | Python:
27 |
28 | ```python
29 | class Person:
30 | def __init__(self, name, age):
31 | self.name = name
32 | self.age = age
33 | ```
34 |
35 | And the OCaml binding....
36 |
37 | ```ocaml
38 | val __init__ : name:string -> age:int -> unit -> t
39 | ```
40 |
41 | If you want to generate functions that ensure the class is correct, you can return `t option` or `t Or_error.t` instead.
42 |
43 | ### Using a different name
44 |
45 | You can use a more natural name for the `__init__` function. E.g., something like `create` by using [attributs](./ocaml-attributes.md).
46 |
47 | ```ocaml
48 | val create : name:string -> age:int -> unit -> t
49 | [@@py_fun_name __init__]
50 | ```
51 |
52 | ## Functions
53 |
54 | You can also bind functions that are not associated with a class.
55 |
56 | The rules are the same for the class and static methods. To tell `pyml_bindgen` that you are actually binding module functions rather than class methods, you have to pass in a command line option `--associated-with module`.
57 |
--------------------------------------------------------------------------------
/_docs_src/src/todo.md:
--------------------------------------------------------------------------------
1 | # To do
2 |
3 | Whoops, you hit a page that isn't finished yet!
4 |
5 | This documentation is a work in progress. If you feel as though
6 | something is missing, feel free to [open an
7 | issue](https://github.com/mooreryan/ocaml_python_bindgen/issues)
8 | on the [pyml_bindgen](https://github.com/mooreryan/ocaml_python_bindgen)
9 | repository.
10 |
--------------------------------------------------------------------------------
/_docs_src/src/tuples.md:
--------------------------------------------------------------------------------
1 | # Handling Tuples
2 |
3 | As of version 0.3.0, you can handle certain types of tuples directly.
4 |
5 | - You can now bind tuples with 2, 3, 4, or 5 elements.
6 | - They can be passed in as arguments, or returned from functions.
7 | - Only basic types and Python objects are allowed in tuples.
8 | - You can also put tuples inside of collections, e.g., `(int * string) list`, but not Options or Or_errors.
9 |
10 | If you need something more complicated then that, you will have to use some of the same tricks I talk about in the [dictionaries](dictionaries.md) or [dictionaries-2](dictionaries-2.md) help pages.
11 |
12 | ## Examples
13 |
14 | You can find examples of binding tuples [here](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/tuples).
15 |
--------------------------------------------------------------------------------
/_docs_src/src/types.md:
--------------------------------------------------------------------------------
1 | # Types
2 |
3 | Not all OCaml types are allowed.
4 |
5 | There are a lot of [tests](https://github.com/mooreryan/pyml_bindgen/tree/main/test) that exercise the rules here.
6 |
7 | ## Function arguments
8 |
9 | For function arguments, you can use
10 |
11 | - `float`
12 | - `string`
13 | - `bool`
14 | - `t` (i.e., the main type of the current module)
15 | - Other module types (e.g., `Span.t`, `Doc.t`, `Yummy.Apple_pie.t`)
16 | - Arrays of any of the above types
17 | - Lists of any of the above types
18 | - Seq.t of any of the above types
19 | - `'a option`, `'a option array`, `'a option list`, `'a option Seq.t`
20 | - `Pytypes.pyobject` or `Py.Object.t` if you need to deal with `pytypes` directly
21 | - Certain kinds of [tuples](tuples.md)
22 |
23 | Note that your custom types must be newly minted modules. E.g.,
24 |
25 | ```ocaml
26 | (* This is okay *)
27 | module Doc = struct
28 | type t
29 | let of_pyobject ...
30 | let to_pyobject ...
31 | ...
32 | end
33 |
34 | (* But this is not *)
35 | type doc
36 | let doc_of_pyobject ...
37 | let doc_to_pyobject ...
38 | ```
39 |
40 | ## Return types
41 |
42 | For return types, you can use all of the above types plus `unit`, and `'a Or_error.t` for types `'a` other than `unit`. However, you cannot use `unit array`, `unit list`, or `unit Seq.t`.
43 |
44 | You can also return many kinds of tuples directly. See [here](tuples.md).
45 |
46 | ## Nesting
47 |
48 | Note: currently, you're not allowed to have **nested** `array`, `list`, `Seq.t`, or `Or_error.t`. If you need them, you will have to bind those functions by hand :)
49 |
50 | E.g., `'a array list` will fail.
51 |
52 | You are allowed to nest `'a option` in arrays, lists, and `Seq.t`s (e.g., `'a option list`); however, this will not work with `Or_error.t`.
53 |
54 | ## Pytypes
55 |
56 | Sometimes you may want to deal directly with `Pytypes.pyobject` (a.k.a. `Py.Object.t`).
57 |
58 | Maybe you have a Python function that is truly polymorphic, or you just don't feel like giving a function a specific OCaml type for whatever reason. Regardless, you can use `Pytypes.pyobject` or `Py.Object.t` for this.
59 |
60 | Of course, you will be leaking a bit of the `pyml` implementation into your API, but sometimes that is unavoidable, or just more convenient than dealing with it in another way.
61 |
62 | Note that you currently are not allowed to nest `pytypes` in any of the containers or monads.
63 |
64 | ## Tuples
65 |
66 | You can handle many kinds of tuples directly. See [here](tuples.md).
67 |
68 | ## Dictionaries
69 |
70 | See [here](dictionaries.md) and [here](dictionaries-2.md) for examples of binding dictionaries.
71 |
72 | Alternatively, you could mark them as `Pytypes.pyobject` or `Py.Object.t` and let the caller deal with them in some way.
73 |
74 | ## Placeholders
75 |
76 | There are two placeholders you can use: `todo` and `not_implemented`.
77 |
78 | If you're binding a large library and you aren't planning on implementing a function, but you want it in the signature for whatever reason, you can use `not_implemented`. If you are planning to come back and implement a function later, you can use `todo`.
79 |
80 | ```ocaml
81 | val f : 'a todo
82 | val g : 'a not_implemented
83 | ```
84 |
85 | These are special in that you can't just use them anywhere, it has to be exactly as above.
86 |
87 | The generated functions for the above signatures will be like this:
88 |
89 | ```ocaml
90 | let f () = failwith "todo: f"
91 | let g () = failwith "not implemented: g"
92 | ```
93 |
94 | So if a user actually calls these functions, the program will fail at runtime.
95 |
--------------------------------------------------------------------------------
/bench/adder.py:
--------------------------------------------------------------------------------
1 | class Adder:
2 | @staticmethod
3 | def add(x, y):
4 | return x + y
5 |
--------------------------------------------------------------------------------
/bench/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (enabled_if
3 | (= %{profile} dev))
4 | (names bench_import_module)
5 | (libraries core core_bench core_unix.command_unix pyml))
6 |
7 | ; TODO: clean up the unused stuff rather than silence warning here
8 |
9 | (env
10 | (dev
11 | (flags
12 | (:standard -w -32))))
13 |
14 | ;;; Uncomment this if you need to regenerate the lib.
15 | ; (rule
16 | ; (target lib.ml)
17 | ; (mode
18 | ; (promote (until-clean)))
19 | ; (action
20 | ; (progn
21 | ; (with-stdout-to
22 | ; lib.ml
23 | ; (progn
24 | ; (run %{bin:pyml_bindgen} val_specs.txt adder Adder --caml-module Adder)
25 | ; (run %{bin:pyml_bindgen} val_specs.txt adder Adder --caml-module Adder_embedded --embed-python-source=adder.py)))
26 | ; (run ocamlformat --inplace --name lib.ml lib.ml))))
27 |
--------------------------------------------------------------------------------
/bench/val_specs.txt:
--------------------------------------------------------------------------------
1 | val add : x:int -> y:int -> unit -> int
2 |
--------------------------------------------------------------------------------
/bin/combine_rec_modules.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | module Cli = struct
4 | open Cmdliner
5 |
6 | (* Use string rather than file so we can do the nicer file checking. *)
7 | let files_term =
8 | let doc =
9 | "OCaml source files. You can also pass in /dev/stdin to read from \
10 | standard input."
11 | in
12 | Arg.(non_empty & pos_all string [] & info [] ~docv:"FILE" ~doc)
13 |
14 | let info =
15 | let doc = "combine recursive modules into a single file" in
16 | let man =
17 | [
18 | `S Manpage.s_description;
19 | `P
20 | "You often need to generate recursive modules when binding cyclic \
21 | Python classes. Since pyml_bindgen doesn't allow you to generate \
22 | recursive modules automatically, you can use this tool to convert \
23 | them. It combines multiple pyml_bindgen generated OCaml modules \
24 | into a single file.";
25 | `P
26 | "While you could combine generated modules by hand, this tool helps \
27 | speed up the process when combining a lot of modules, or when you \
28 | need to automate the process (e.g., in a Dune rule or shell \
29 | script).";
30 | `S Manpage.s_examples;
31 | `P
32 | "Imagine you generated the files a.ml and b.ml with pyml_bindgen. \
33 | Module A refers to module B and module B refers to module A, so \
34 | they are recursive modules. However, pyml_bindgen doesn't \
35 | automatically generate them as recursive modules. Instead, you can \
36 | use combine_rec_modules.";
37 | `P "a.ml contents:";
38 | `Pre " module A : sig ... end = struct ... end";
39 | `P "b.ml contents:";
40 | `Pre " module B : sig ... end = struct ... end";
41 | `P "Run combine_rec_modules:";
42 | `Pre " \\$ combine_rec_modules a.ml b.ml > lib.ml";
43 | `P "Then lib.ml contents will be:";
44 | `Pre
45 | "module rec A : sig ... end = struct ... end\n\
46 | and B : sig ... end = struct ... end";
47 | `P "===";
48 | `P
49 | "You will often use this program with combine_rec_modules and \
50 | ocamlformat.";
51 | `Pre
52 | " \\$ gen_multi cli_specs.tsv \\\\ \n\
53 | \ | combine_rec_modules /dev/stdin \\\\ \n\
54 | \ | ocamlformat --name a.ml - \\\\ \n\
55 | \ > lib.ml";
56 | `S Manpage.s_bugs;
57 | `P
58 | "Please report any bugs or issues on GitHub. \
59 | (https://github.com/mooreryan/pyml_bindgen/issues)";
60 | `S Manpage.s_see_also;
61 | `P
62 | "For full documentation, please see the GitHub page. \
63 | (https://github.com/mooreryan/pyml_bindgen)";
64 | `S Manpage.s_authors;
65 | `P "Ryan M. Moore ";
66 | ]
67 | in
68 | Cmd.info "combine_rec_modules" ~version:Lib.Version.version ~doc ~man
69 | ~exits:[]
70 |
71 | let parse_argv () =
72 | match Cmd.eval_value @@ Cmd.v info files_term with
73 | | Ok (`Ok files) -> Ok files
74 | | Ok `Help | Ok `Version -> Error 0
75 | | Error _ -> Error 1
76 | end
77 |
78 | let module_re = Re.Perl.compile_pat "module ([a-zA-Z0-9_]+)"
79 |
80 | let first_module name = "module rec " ^ name
81 |
82 | let non_first_module name = "and " ^ name
83 |
84 | let fix_module_line modules_seen group =
85 | let module_name = Re.Group.get group 1 in
86 | let is_first_module = modules_seen = 0 in
87 | if is_first_module then first_module module_name
88 | else non_first_module module_name
89 |
90 | let process_module_line modules_seen line =
91 | let new_line = Re.replace module_re line ~f:(fix_module_line modules_seen) in
92 | Stdio.print_endline new_line;
93 | modules_seen + 1
94 |
95 | let process_non_module_line modules_seen line =
96 | Stdio.print_endline line;
97 | modules_seen
98 |
99 | let process_line modules_seen line =
100 | if Re.execp module_re line then process_module_line modules_seen line
101 | else process_non_module_line modules_seen line
102 |
103 | let process_file modules_seen fname =
104 | Stdio.In_channel.with_file fname ~f:(fun ic ->
105 | Stdio.In_channel.fold_lines ic ~init:modules_seen ~f:process_line)
106 |
107 | let check_fnames fnames =
108 | let errors =
109 | List.filter_map fnames ~f:(fun fname ->
110 | if Caml.Sys.file_exists fname then None else Some fname)
111 | in
112 | match errors with
113 | | [] -> ()
114 | | [ fname ] ->
115 | let msg = [%string "ERROR -- File %{fname} does not exist"] in
116 | Bin_utils.exit ~code:1 msg
117 | | fnames ->
118 | let fnames = String.concat fnames ~sep:", " in
119 | let msg = [%string "ERROR -- These files do not exist: %{fnames}"] in
120 | Bin_utils.exit ~code:1 msg
121 |
122 | (* TODO this will exit with an error code, but stuff will still be printed to
123 | the outfile. Could be confusing. *)
124 | let check_modules_seen = function
125 | | 0 ->
126 | Bin_utils.exit ~code:1
127 | [%string "ERROR -- I didn't see any modules in the input files"]
128 | | 1 ->
129 | Bin_utils.exit ~code:1
130 | [%string "ERROR -- I only saw one module in the input files"]
131 | | _n -> ()
132 |
133 | let run fnames =
134 | check_fnames fnames;
135 | let modules_seen = List.fold fnames ~init:0 ~f:process_file in
136 | check_modules_seen modules_seen
137 |
138 | let main () =
139 | match Cli.parse_argv () with
140 | | Ok fnames -> run fnames
141 | | Error exit_code -> Caml.exit exit_code
142 |
143 | let () = main ()
144 |
--------------------------------------------------------------------------------
/bin/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (package pyml_bindgen)
3 | (public_names pyml_bindgen combine_rec_modules gen_multi)
4 | (names pyml_bindgen combine_rec_modules gen_multi)
5 | (libraries base cmdliner lib re stdio)
6 | (instrumentation
7 | (backend bisect_ppx))
8 | (preprocess
9 | (pps ppx_let ppx_string)))
10 |
--------------------------------------------------------------------------------
/bin/gen_multi.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | module Cli = struct
4 | open Cmdliner
5 |
6 | let file_term =
7 | let doc =
8 | "TSV file with CLI opts. The first line (header) is ignored. The \
9 | columns should be should be: signatures [tab] py_module [tab] py_class \
10 | [tab] associated_with [tab] caml_module [tab] embed_python_source [tab] \
11 | of_pyo_ret_type."
12 | in
13 | Arg.(
14 | required & pos 0 (some non_dir_file) None & info [] ~docv:"CLI_OPTS" ~doc)
15 |
16 | let info =
17 | let doc = "generate multiple pyml_bindgen bindings with one command" in
18 | let man =
19 | [
20 | `S Manpage.s_description;
21 | `P
22 | "In some cases, it can be simpler to use a single command to \
23 | generate multiple bindings.";
24 | `P
25 | "The input file is a 7 column TSV file. Each column matches one of \
26 | the arguments to pyml_bindgen that you would give on the command \
27 | line. The order of the columns must be: signatures, py_module, \
28 | py_class, associated_with, caml_module, embed_python_source, and \
29 | of_pyo_return_type.";
30 | `P
31 | "You fill in the columns more or less in the same way as you would \
32 | pass things in to pyml_bindgen. For optional flags, if you don't \
33 | want to apply then, you can leave them blank, or you can use NA or \
34 | na.";
35 | `S Manpage.s_examples;
36 | `P
37 | "You will often use this program with combine_rec_modules and \
38 | ocamlformat.";
39 | `Pre
40 | " \\$ gen_multi cli_specs.tsv \\\\ \n\
41 | \ | combine_rec_modules /dev/stdin \\\\ \n\
42 | \ | ocamlformat --name a.ml - \\\\ \n\
43 | \ > lib.ml";
44 | `S Manpage.s_bugs;
45 | `P
46 | "Please report any bugs or issues on GitHub. \
47 | (https://github.com/mooreryan/pyml_bindgen/issues)";
48 | `S Manpage.s_see_also;
49 | `P
50 | "For full documentation, please see the GitHub page. \
51 | (https://github.com/mooreryan/pyml_bindgen)";
52 | `S Manpage.s_authors;
53 | `P "Ryan M. Moore ";
54 | ]
55 | in
56 | Cmd.info "gen_multi" ~version:Lib.Version.version ~doc ~man ~exits:[]
57 |
58 | let parse_argv () =
59 | match Cmd.eval_value @@ Cmd.v info file_term with
60 | | Ok (`Ok files) -> Ok files
61 | | Ok `Help | Ok `Version -> Error 0
62 | | Error _ -> Error 1
63 | end
64 |
65 | let ok_or_abort x =
66 | Or_error.iter_error x ~f:(fun err ->
67 | Bin_utils.exit ~code:1 @@ Error.to_string_hum err)
68 |
69 | (* TODO just fix the run function so it won't raise... *)
70 | let run_wrapper opts =
71 | Or_error.join @@ Or_error.try_with (fun () -> Bin_utils.run opts)
72 |
73 | (* Each row of the input file is a pyml_bindgen CLI specification. *)
74 | let get_cli_opts fname =
75 | let open Stdio in
76 | let open Or_error.Let_syntax in
77 | let opts =
78 | In_channel.with_file fname ~f:(fun ic ->
79 | Or_error.all @@ List.rev @@ snd
80 | @@ In_channel.fold_lines ic ~init:(0, []) ~f:(fun (i, things) line ->
81 | if i > 0 then (i + 1, Main_cli.opts_of_string line :: things)
82 | else (i + 1, things)))
83 | in
84 | match%bind opts with
85 | | [] -> Or_error.error_string "Got no options"
86 | | opts -> Or_error.return opts
87 |
88 | let run fname =
89 | match get_cli_opts fname with
90 | | Ok opts -> ok_or_abort @@ Or_error.all_unit @@ List.map opts ~f:run_wrapper
91 | | Error err -> Bin_utils.exit ~code:1 (Error.to_string_hum err)
92 |
93 | let main () =
94 | match Cli.parse_argv () with
95 | | Ok fname -> run fname
96 | | Error exit_code -> Caml.exit exit_code
97 |
98 | let () = main ()
99 |
--------------------------------------------------------------------------------
/bin/main_cli.mli:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | type opts = {
4 | signatures : string;
5 | py_module : string;
6 | py_class : string;
7 | associated_with : [ `Class | `Module ];
8 | caml_module : string option;
9 | split_caml_module : string option;
10 | embed_python_source : string option;
11 | of_pyo_ret_type : [ `No_check | `Option | `Or_error ];
12 | }
13 |
14 | val opts_of_string : string -> opts Or_error.t
15 |
16 | val parse_argv : unit -> (opts, int) Result.t
17 | (** If successful, return the [opts]. If failure, return exit code. *)
18 |
--------------------------------------------------------------------------------
/bin/pyml_bindgen.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | (* TODO better to move this check into the Cmdliner section. *)
4 | let check_opts (opts : Main_cli.opts) =
5 | match (opts.caml_module, opts.split_caml_module) with
6 | | None, Some _ ->
7 | Bin_utils.exit ~code:1
8 | "ERROR: --split-caml-module was given but --caml-module was not"
9 | | None, None | Some _, None | Some _, Some _ -> ()
10 |
11 | let main () =
12 | match Main_cli.parse_argv () with
13 | | Ok opts -> (
14 | check_opts opts;
15 | match Bin_utils.run opts with
16 | | Ok _ -> Caml.exit 0
17 | (* TODO non-zero exit code here? *)
18 | | Error err -> Stdio.prerr_endline @@ Error.to_string_hum err)
19 | | Error exit_code -> Caml.exit exit_code
20 |
21 | let () = main ()
22 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
3 | (generate_opam_files true)
4 |
5 | (cram enable)
6 |
7 | (name pyml_bindgen)
8 |
9 | (version 0.4.1)
10 |
11 | (maintainers "Ryan M. Moore")
12 |
13 | (authors "Ryan M. Moore")
14 |
15 | (homepage "https://github.com/mooreryan/ocaml_python_bindgen")
16 |
17 | (source
18 | (github mooreryan/ocaml_python_bindgen))
19 |
20 | (documentation "https://mooreryan.github.io/ocaml_python_bindgen/")
21 |
22 | (bug_reports "https://github.com/mooreryan/ocaml_python_bindgen/issues")
23 |
24 | (package
25 | (name pyml_bindgen-dev)
26 | (synopsis "Development package for pyml_bindgen")
27 | (allow_empty)
28 | (depends
29 | (ocaml
30 | (>= "4.14"))
31 | bisect_ppx
32 | (core
33 | (>= "v0.15"))
34 | (core_bench
35 | (>= "v0.15"))
36 | (core_unix
37 | (>= "v0.15"))
38 | (ocamlformat
39 | (and
40 | (>= "0.23")
41 | (< "0.24")))
42 | (ocaml-lsp-server
43 | (>= "1.13"))
44 | pyml))
45 |
46 | (package
47 | (name pyml_bindgen)
48 | (synopsis "Generate pyml bindings from OCaml value specifications")
49 | (depends
50 | ; Runtime deps
51 | (angstrom
52 | (>= "0.15.0"))
53 | (base
54 | (>= "v0.12"))
55 | (cmdliner
56 | (>= "1.1.0"))
57 | (ppx_let
58 | (>= "v0.12"))
59 | (ppx_sexp_conv
60 | (>= "v0.12"))
61 | (ppx_string
62 | (>= "v0.12"))
63 | (re
64 | (>= "1.10.0"))
65 | (stdio
66 | (>= "v0.12"))
67 | (ocaml
68 | (>= "4.08.0"))
69 | ; Test-only deps
70 | (conf-python-3-dev
71 | (and
72 | (>= "1")
73 | :with-test))
74 | (base_quickcheck
75 | (and
76 | (>= "v0.12")
77 | :with-test))
78 | (ocamlformat
79 | (and
80 | (>= "0.23")
81 | (< "0.24")
82 | :with-test))
83 | (ppx_assert
84 | (and
85 | (>= "v0.12")
86 | :with-test))
87 | (ppx_inline_test
88 | (and
89 | (>= "v0.12")
90 | :with-test))
91 | (ppx_expect
92 | (and
93 | (>= "v0.12")
94 | :with-test))
95 | (pyml :with-test)
96 | (shexp
97 | (and
98 | (>= "v0.14")
99 | :with-test))))
100 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | In this directory you will find some full examples. Each example contains
4 |
5 | * Value specification files to specify the bindings
6 | * Python code/modules that we want to bind
7 | * Tests that are run whenever you run `dune test` in the source directory
8 | * Automatic generation of OCaml bindings using Dune [rules](https://dune.readthedocs.io/en/stable/dune-files.html#rule)
9 |
10 | The auto-generation of bindings is particularly sweet! If you update any of the spec files, dune will pick up the change and regenerate the OCaml binding code automatically when you run `dune build`!
11 |
--------------------------------------------------------------------------------
/examples/attributes/README.md:
--------------------------------------------------------------------------------
1 | # Attributes
2 |
3 | This directory has an example of using the `py_fun_name` and `py_arg_name` attributes.
4 |
5 | ## `py_fun_name`
6 |
7 | `py_fun_name` allows you to use different names for functions on the OCaml side than the original Python library had used.
8 |
9 | For example, rather than having a `Cat.__init__` method to call from the OCaml side (i.e., to make a new Python `Cat` object), you can bind the Python `__init__` function to something more idiomatic like `create` or whatever you want.
10 |
11 | ```ocaml
12 | val create : t -> ...
13 | [@@py_fun_name __init__]
14 | ```
15 |
16 | Another common use for this is to bind the Python `__str__` method for a class to `to_string` on the OCaml side.
17 |
18 | You can do this with any function. One reason is that you may want to have some type safety with a polymorphic Python function. While you could pass in [Py.Object.t](https://mooreryan.github.io/ocaml_python_bindgen/types/#pytypes) directly, you could also use attributes to bind multiple OCaml functions to the same Python method. E.g.,
19 |
20 | ```ocaml
21 | val eat : t -> num_mice:int -> unit -> unit
22 |
23 | val eat_part : t -> num_mice:float -> unit -> unit
24 | [@@py_fun_name eat]
25 | ```
26 |
27 | In this case, we have one `eat` function for `int` and one for `float`.
28 |
29 | ## `py_arg_name`
30 |
31 | `py_arg_name` allows you to use different argument names on the OCaml side from those that are used on the Python side.
32 |
33 | For example, you may have a Python function that has 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.
34 |
35 | ```ocaml
36 | val f : t -> method_:string -> unit -> string
37 | [@@py_arg_name method_ method]
38 | ```
39 |
40 | As you see, 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 (i.e., as it will be called in Python).
41 |
42 | ## Multiple attributes
43 |
44 | You can use multiple attributes on a single val spec. Here is one from this example project.
45 |
46 | ```ocaml
47 | val say_this : t -> w:string -> x:string -> y:string -> z:string -> unit -> string
48 | [@@py_fun_name say]
49 | [@@py_arg_name w a]
50 | [@@py_arg_name x b]
51 | [@@py_arg_name y c]
52 | [@@py_arg_name z d]
53 | ```
54 |
55 | Here is the python function.
56 |
57 | ```python
58 | class Cat:
59 |
60 | ...
61 |
62 | def say(self, a, b, c, d):
63 | return(f'{self.name} says {a}, {b}, {c} and {d}.')
64 | ```
65 |
66 | As you see, the Python function is called `say` and not `say_this`. Also, the arguments to the Python function are `a`, `b`, `c`, and `d`, whereas we bind them to `w`, `x`, `y`, and `z` on the OCaml side.
67 |
68 | _Note: Multiple attributes on the same value spec must go on separate lines._
69 |
--------------------------------------------------------------------------------
/examples/attributes/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name run)
5 | (libraries examples_attributes_lib pyml))
6 |
--------------------------------------------------------------------------------
/examples/attributes/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/examples/attributes/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name examples_attributes_lib)
5 | (libraries pyml))
6 |
7 | (rule
8 | ;; The file produced by this rule will be called `examples_attributes_lib.ml`.
9 | (target examples_attributes_lib.ml)
10 | ;; Put the resulting examples_attributes_lib.ml file in this directory. When you run
11 | ;; `dune clean`, it will be removed.
12 | (mode
13 | (promote (until-clean)))
14 | ;; We need to give the action access to the files in the `specs` dir.
15 | (deps
16 | (source_tree specs)
17 | ;; We also need to give access to the Python source directory.
18 | (source_tree ../py))
19 | (action
20 | ;; `pyml_bindgen` outputs directly to stdout, so we need to redirect
21 | ;; the stdout to `examples_attributes_lib.ml`. We do this using `with-stdout-to`.
22 | (with-stdout-to
23 | ;; This is the name of the file to which the output of the
24 | ;; following command will be directed.
25 | examples_attributes_lib.ml
26 | ;; This step isn't necessary, but I use `ocamlformat` for
27 | ;; everything, so I would like to have the output of
28 | ;; `pyml_bindgen` also processed by `ocamlformat`. `pipe-stdout`
29 | ;; takes the stdout of the first `run` command and pipes it to the
30 | ;; stdin of the second `run` command.
31 | (pipe-stdout
32 | ;; Here is the acutal `pyml_bindgen` command to generate the
33 | ;; bindings.
34 | (run
35 | %{bin:pyml_bindgen}
36 | specs/cat.txt
37 | cat
38 | Cat
39 | --caml-module
40 | Cat
41 | --of-pyo-ret-type
42 | no_check
43 | ;; This option tells `pyml_bindgen` that you want to embed
44 | ;; the Python code into the generated OCaml module. It will
45 | ;; be evaluated at runtime. Note how we refer to the path as
46 | ;; specified in `source_tree` above.
47 | --embed-python-source
48 | ../py/cat.py)
49 | ;; And finally, process the bindings code with ocamlformat.
50 | (run ocamlformat --name examples_attributes_lib.ml -)))))
51 |
--------------------------------------------------------------------------------
/examples/attributes/lib/examples_attributes_lib.ml:
--------------------------------------------------------------------------------
1 | module Cat : sig
2 | type t
3 |
4 | val of_pyobject : Pytypes.pyobject -> t
5 | val to_pyobject : t -> Pytypes.pyobject
6 | val create : name:string -> unit -> t
7 | val to_string : t -> unit -> string
8 | val eat : t -> num_mice:int -> unit -> unit
9 | val eat_part : t -> num_mice:float -> unit -> unit
10 | val jump : t -> how_high:int -> unit -> unit
11 | val climb : t -> how_high:int -> unit -> unit
12 |
13 | val say_this :
14 | t -> w:string -> x:string -> y:string -> z:string -> unit -> string
15 | end = struct
16 | let filter_opt l = List.filter_map Fun.id l
17 |
18 | let py_module =
19 | lazy
20 | (let source =
21 | {pyml_bindgen_string_literal|class Cat:
22 | def __init__(self, name):
23 | self.name = name
24 | self.hunger = 0
25 |
26 | def __str__(self):
27 | return(f'Cat -- name: {self.name}, hunger: {self.hunger}')
28 |
29 | def eat(self, num_mice=1):
30 | self.hunger -= (num_mice * 5)
31 | if self.hunger < 0:
32 | self.hunger = 0
33 |
34 | def jump(self, how_high=1):
35 | if how_high > 0:
36 | self.hunger += how_high
37 |
38 | def say(self, a, b, c, d):
39 | return(f'{self.name} says {a}, {b}, {c} and {d}.')
40 | |pyml_bindgen_string_literal}
41 | in
42 | let filename =
43 | {pyml_bindgen_string_literal|../py/cat.py|pyml_bindgen_string_literal}
44 | in
45 | let bytecode = Py.compile ~filename ~source `Exec in
46 | Py.Import.exec_code_module
47 | {pyml_bindgen_string_literal|cat|pyml_bindgen_string_literal} bytecode)
48 |
49 | let import_module () = Lazy.force py_module
50 |
51 | type t = Pytypes.pyobject
52 |
53 | let of_pyobject pyo = pyo
54 | let to_pyobject x = x
55 |
56 | let create ~name () =
57 | let callable = Py.Module.get (import_module ()) "Cat" in
58 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
59 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
60 |
61 | let to_string t () =
62 | let callable = Py.Object.find_attr_string t "__str__" in
63 | let kwargs = filter_opt [] in
64 | Py.String.to_string
65 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
66 |
67 | let eat t ~num_mice () =
68 | let callable = Py.Object.find_attr_string t "eat" in
69 | let kwargs = filter_opt [ Some ("num_mice", Py.Int.of_int num_mice) ] in
70 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
71 |
72 | let eat_part t ~num_mice () =
73 | let callable = Py.Object.find_attr_string t "eat" in
74 | let kwargs = filter_opt [ Some ("num_mice", Py.Float.of_float num_mice) ] in
75 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
76 |
77 | let jump t ~how_high () =
78 | let callable = Py.Object.find_attr_string t "jump" in
79 | let kwargs = filter_opt [ Some ("how_high", Py.Int.of_int how_high) ] in
80 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
81 |
82 | let climb t ~how_high () =
83 | let callable = Py.Object.find_attr_string t "jump" in
84 | let kwargs = filter_opt [ Some ("how_high", Py.Int.of_int how_high) ] in
85 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
86 |
87 | let say_this t ~w ~x ~y ~z () =
88 | let callable = Py.Object.find_attr_string t "say" in
89 | let kwargs =
90 | filter_opt
91 | [
92 | Some ("a", Py.String.of_string w);
93 | Some ("b", Py.String.of_string x);
94 | Some ("c", Py.String.of_string y);
95 | Some ("d", Py.String.of_string z);
96 | ]
97 | in
98 | Py.String.to_string
99 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
100 | end
101 |
--------------------------------------------------------------------------------
/examples/attributes/lib/specs/cat.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t
2 | [@@py_fun_name __init__]
3 |
4 | val to_string : t -> unit -> string
5 | [@@py_fun_name __str__]
6 |
7 | val eat : t -> num_mice:int -> unit -> unit
8 |
9 | val eat_part : t -> num_mice:float -> unit -> unit
10 | [@@py_fun_name eat]
11 |
12 | val jump : t -> how_high:int -> unit -> unit
13 |
14 | val climb : t -> how_high:int -> unit -> unit
15 | [@@py_fun_name jump]
16 |
17 | val say_this : t -> w:string -> x:string -> y:string -> z:string -> unit -> string
18 | [@@py_fun_name say]
19 | [@@py_arg_name w a]
20 | [@@py_arg_name x b]
21 | [@@py_arg_name y c]
22 | [@@py_arg_name z d]
--------------------------------------------------------------------------------
/examples/attributes/py/cat.py:
--------------------------------------------------------------------------------
1 | class Cat:
2 | def __init__(self, name):
3 | self.name = name
4 | self.hunger = 0
5 |
6 | def __str__(self):
7 | return(f'Cat -- name: {self.name}, hunger: {self.hunger}')
8 |
9 | def eat(self, num_mice=1):
10 | self.hunger -= (num_mice * 5)
11 | if self.hunger < 0:
12 | self.hunger = 0
13 |
14 | def jump(self, how_high=1):
15 | if how_high > 0:
16 | self.hunger += how_high
17 |
18 | def say(self, a, b, c, d):
19 | return(f'{self.name} says {a}, {b}, {c} and {d}.')
20 |
--------------------------------------------------------------------------------
/examples/attributes/run.ml:
--------------------------------------------------------------------------------
1 | open Examples_attributes_lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let cat = Cat.create ~name:"Sam" ()
6 |
7 | let () = Cat.climb cat ~how_high:20 ()
8 |
9 | let () = Cat.eat_part cat ~num_mice:0.2 ()
10 |
11 | let () = Cat.eat cat ~num_mice:2 ()
12 |
13 | let () = print_endline @@ Cat.to_string cat ()
14 |
15 | let () =
16 | print_endline
17 | @@ Cat.say_this cat ~w:"first" ~x:"second" ~y:"third" ~z:"fourth" ()
18 |
19 | let () = print_endline "done"
20 |
--------------------------------------------------------------------------------
/examples/attributes/test/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps ../run.exe))
5 |
--------------------------------------------------------------------------------
/examples/attributes/test/run.t:
--------------------------------------------------------------------------------
1 | Run the executable.
2 |
3 | $ ../run.exe
4 | Cat -- name: Sam, hunger: 9.0
5 | Sam says first, second, third and fourth.
6 | done
7 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/README.md:
--------------------------------------------------------------------------------
1 | # Embedding Python Source Code
2 |
3 | This example is pretty similar to the [quick start](https://github.com/mooreryan/ocaml_python_bindgen/tree/main/examples/quick_start) example.
4 |
5 | This one shows you how to embed the Python code directly into the generated OCaml module. It will then be evaluated at run time.
6 |
7 | To do this, you can use the `--embed-python-source` option. See this library's `dune` file for how to set it up.
8 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name run)
5 | (libraries embedding_py_source_lib pyml))
6 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name embedding_py_source_lib)
5 | (libraries pyml))
6 |
7 | (rule
8 | ;; The file produced by this rule will be called `math.ml`.
9 | (target math.ml)
10 | ;; Put the resulting math.ml file in this directory. When you run
11 | ;; `dune clean`, it will be removed.
12 | (mode
13 | (promote (until-clean)))
14 | ;; We need to give the action access to the files in the `specs` dir.
15 | (deps
16 | (source_tree specs)
17 | ;; We also need to give access to the Python source directory.
18 | (source_tree ../py))
19 | (action
20 | ;; `pyml_bindgen` outputs directly to stdout, so we need to redirect
21 | ;; the stdout to `math.ml`. We do this using `with-stdout-to`.
22 | (with-stdout-to
23 | ;; This is the name of the file to which the output of the
24 | ;; following command will be directed.
25 | math.ml
26 | ;; This step isn't necessary, but I use `ocamlformat` for
27 | ;; everything, so I would like to have the output of
28 | ;; `pyml_bindgen` also processed by `ocamlformat`. `pipe-stdout`
29 | ;; takes the stdout of the first `run` command and pipes it to the
30 | ;; stdin of the second `run` command.
31 | (pipe-stdout
32 | ;; Here is the acutal `pyml_bindgen` command to generate the
33 | ;; bindings.
34 | (run
35 | %{bin:pyml_bindgen}
36 | specs/adder.txt
37 | adder
38 | Adder
39 | --caml-module
40 | Adder
41 | ;; This option tells `pyml_bindgen` that you want to embed
42 | ;; the Python code into the generated OCaml module. It will
43 | ;; be evaluated at runtime. Note how we refer to the path as
44 | ;; specified in `source_tree` above.
45 | --embed-python-source
46 | ../py/math/adder.py)
47 | ;; And finally, process the bindings code with ocamlformat.
48 | (run ocamlformat --name math.ml -)))))
49 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/lib/math.ml:
--------------------------------------------------------------------------------
1 | module Adder : sig
2 | type t
3 |
4 | val of_pyobject : Pytypes.pyobject -> t option
5 | val to_pyobject : t -> Pytypes.pyobject
6 | val add : x:int -> y:int -> unit -> int
7 | end = struct
8 | let filter_opt l = List.filter_map Fun.id l
9 |
10 | let py_module =
11 | lazy
12 | (let source =
13 | {pyml_bindgen_string_literal|class Adder:
14 | @staticmethod
15 | def add(x, y):
16 | return x + y
17 | |pyml_bindgen_string_literal}
18 | in
19 | let filename =
20 | {pyml_bindgen_string_literal|../py/math/adder.py|pyml_bindgen_string_literal}
21 | in
22 | let bytecode = Py.compile ~filename ~source `Exec in
23 | Py.Import.exec_code_module
24 | {pyml_bindgen_string_literal|adder|pyml_bindgen_string_literal}
25 | bytecode)
26 |
27 | let import_module () = Lazy.force py_module
28 |
29 | type t = Pytypes.pyobject
30 |
31 | let is_instance pyo =
32 | let py_class = Py.Module.get (import_module ()) "Adder" in
33 | Py.Object.is_instance pyo py_class
34 |
35 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
36 | let to_pyobject x = x
37 |
38 | let add ~x ~y () =
39 | let class_ = Py.Module.get (import_module ()) "Adder" in
40 | let callable = Py.Object.find_attr_string class_ "add" in
41 | let kwargs =
42 | filter_opt [ Some ("x", Py.Int.of_int x); Some ("y", Py.Int.of_int y) ]
43 | in
44 | Py.Int.to_int @@ Py.Callable.to_function_with_keywords callable [||] kwargs
45 | end
46 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/lib/specs/adder.txt:
--------------------------------------------------------------------------------
1 | val add : x:int -> y:int -> unit -> int
2 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/py/math/adder.py:
--------------------------------------------------------------------------------
1 | class Adder:
2 | @staticmethod
3 | def add(x, y):
4 | return x + y
5 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/run.ml:
--------------------------------------------------------------------------------
1 | open Embedding_py_source_lib.Math
2 |
3 | let () = Py.initialize ()
4 |
5 | let result = Adder.add ~x:1 ~y:2 ()
6 |
7 | let () = assert (result = 3)
8 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/test/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps ../run.exe))
5 |
--------------------------------------------------------------------------------
/examples/embedding_python_source/test/run.t:
--------------------------------------------------------------------------------
1 | Run the executable.
2 |
3 | $ ../run.exe
4 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/README.md:
--------------------------------------------------------------------------------
1 | # Splitting generated modules
2 |
3 | This example shows how you can use `pyml_bindgen` to generate separate `.ml` and `.mli` files for the implementations and signatures of the generated OCaml modules.
4 |
5 | In the `dune` file, you will see two rules, one to generate `ml` and `mli` files for the `Thing` module, and one to generate `ml` and `mli` files for the `Orange` module.
6 |
7 | To do so, you need to provide both the `--caml-module` and `--split-caml-module` options. Something like this:
8 |
9 | ```bash
10 | $ pyml_bindgen specs.txt thing Thing --caml-module Thing --split-caml-module .
11 | ```
12 |
13 | `--split-caml-module .` says to generate a `thing.ml` and `thing.mli` file in the current directory. You can specify a different directory name to put the generated files in a different directory. E.g., something like `--split-caml-module files/go/here`.
14 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/bin/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name hello)
5 | (libraries pyml base gen_ml_and_mli_lib))
6 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/bin/hello.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Gen_ml_and_mli_lib
3 |
4 | let () = Py.initialize ()
5 |
6 | let thing = Or_error.ok_exn @@ Thing.create ~name:"Ryan" ()
7 |
8 | let () = assert (String.("Ryan" = Thing.name thing))
9 |
10 | let orange = Or_error.ok_exn @@ Orange.create ~flavor:"Sooo good!" ()
11 |
12 | let () = assert (String.("Sooo good!" = Orange.flavor orange))
13 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
3 | (cram enable)
4 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name gen_ml_and_mli_lib)
5 | (libraries pyml base))
6 |
7 | (rule
8 | (targets thing.ml thing.mli)
9 | (mode
10 | (promote (until-clean)))
11 | (deps
12 | (source_tree specs)
13 | (source_tree py))
14 | (action
15 | (progn
16 | (run
17 | %{bin:pyml_bindgen}
18 | specs/thing.txt
19 | thing
20 | Thing
21 | --caml-module
22 | Thing
23 | --split-caml-module
24 | .
25 | --of-pyo-ret-type
26 | or_error
27 | --embed-python-source
28 | py/thing.py)
29 | (run ocamlformat thing.ml --inplace)
30 | (run ocamlformat thing.mli --inplace))))
31 |
32 | (rule
33 | (targets orange.ml orange.mli)
34 | (mode
35 | (promote (until-clean)))
36 | (deps
37 | (source_tree specs)
38 | (source_tree py))
39 | (action
40 | (progn
41 | (run
42 | %{bin:pyml_bindgen}
43 | specs/orange.txt
44 | orange
45 | Orange
46 | --caml-module
47 | Orange
48 | --split-caml-module
49 | .
50 | --of-pyo-ret-type
51 | or_error
52 | --embed-python-source
53 | py/orange.py)
54 | (run ocamlformat orange.ml --inplace)
55 | (run ocamlformat orange.mli --inplace))))
56 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/orange.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | let filter_opt = List.filter_opt
4 |
5 | let py_module =
6 | lazy
7 | (let source =
8 | {pyml_bindgen_string_literal|class Orange:
9 | def __init__(self, flavor):
10 | self.flavor = flavor
11 | |pyml_bindgen_string_literal}
12 | in
13 | let filename =
14 | {pyml_bindgen_string_literal|py/orange.py|pyml_bindgen_string_literal}
15 | in
16 | let bytecode = Py.compile ~filename ~source `Exec in
17 | Py.Import.exec_code_module
18 | {pyml_bindgen_string_literal|orange|pyml_bindgen_string_literal} bytecode)
19 |
20 | let import_module () = Lazy.force py_module
21 |
22 | type t = Pytypes.pyobject
23 |
24 | let is_instance pyo =
25 | let py_class = Py.Module.get (import_module ()) "Orange" in
26 | Py.Object.is_instance pyo py_class
27 |
28 | let of_pyobject pyo =
29 | if is_instance pyo then Or_error.return pyo
30 | else Or_error.error_string "Expected Orange"
31 |
32 | let to_pyobject x = x
33 |
34 | let create ~flavor () =
35 | let callable = Py.Module.get (import_module ()) "Orange" in
36 | let kwargs = filter_opt [ Some ("flavor", Py.String.of_string flavor) ] in
37 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
38 |
39 | let flavor t = Py.String.to_string @@ Py.Object.find_attr_string t "flavor"
40 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/orange.mli:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | type t
4 |
5 | val of_pyobject : Pytypes.pyobject -> t Or_error.t
6 | val to_pyobject : t -> Pytypes.pyobject
7 | val create : flavor:string -> unit -> t Or_error.t
8 | val flavor : t -> string
9 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/py/orange.py:
--------------------------------------------------------------------------------
1 | class Orange:
2 | def __init__(self, flavor):
3 | self.flavor = flavor
4 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/py/thing.py:
--------------------------------------------------------------------------------
1 | class Thing:
2 | def __init__(self, name):
3 | self.name = name
4 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/specs/orange.txt:
--------------------------------------------------------------------------------
1 | val create : flavor:string -> unit -> t Or_error.t
2 | [@@py_fun_name __init__]
3 |
4 | val flavor : t -> string
5 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/specs/thing.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t Or_error.t
2 | [@@py_fun_name __init__]
3 |
4 | val name : t -> string
5 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/thing.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | let filter_opt = List.filter_opt
4 |
5 | let py_module =
6 | lazy
7 | (let source =
8 | {pyml_bindgen_string_literal|class Thing:
9 | def __init__(self, name):
10 | self.name = name
11 | |pyml_bindgen_string_literal}
12 | in
13 | let filename =
14 | {pyml_bindgen_string_literal|py/thing.py|pyml_bindgen_string_literal}
15 | in
16 | let bytecode = Py.compile ~filename ~source `Exec in
17 | Py.Import.exec_code_module
18 | {pyml_bindgen_string_literal|thing|pyml_bindgen_string_literal} bytecode)
19 |
20 | let import_module () = Lazy.force py_module
21 |
22 | type t = Pytypes.pyobject
23 |
24 | let is_instance pyo =
25 | let py_class = Py.Module.get (import_module ()) "Thing" in
26 | Py.Object.is_instance pyo py_class
27 |
28 | let of_pyobject pyo =
29 | if is_instance pyo then Or_error.return pyo
30 | else Or_error.error_string "Expected Thing"
31 |
32 | let to_pyobject x = x
33 |
34 | let create ~name () =
35 | let callable = Py.Module.get (import_module ()) "Thing" in
36 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
37 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
38 |
39 | let name t = Py.String.to_string @@ Py.Object.find_attr_string t "name"
40 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/lib/thing.mli:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | type t
4 |
5 | val of_pyobject : Pytypes.pyobject -> t Or_error.t
6 | val to_pyobject : t -> Pytypes.pyobject
7 | val create : name:string -> unit -> t Or_error.t
8 | val name : t -> string
9 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/test/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps ../bin/hello.exe))
5 |
--------------------------------------------------------------------------------
/examples/gen_ml_and_mli/test/it_works.t:
--------------------------------------------------------------------------------
1 | Check it
2 |
3 | $ ../bin/hello.exe
4 |
--------------------------------------------------------------------------------
/examples/importing_modules/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name run)
5 | (libraries pyml importing_modules_lib))
6 |
--------------------------------------------------------------------------------
/examples/importing_modules/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/examples/importing_modules/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name importing_modules_lib)
5 | (libraries pyml))
6 |
7 | (rule
8 | ;; The file produced by this rule will be called `lib.ml`.
9 | (target magic_dust.ml)
10 | ;; Put the resulting lib.ml file in this directory. When you run
11 | ;; `dune clean`, it will be removed.
12 | (mode
13 | (promote (until-clean)))
14 | ;; We need to give the action access to the files in the `specs` dir.
15 | (deps
16 | (source_tree specs))
17 | (action
18 | ;; We use `progn` to run multiple commands one after the other.
19 | (progn
20 | ;; `with-stdout-to` will collect all standard output of anything
21 | ;; inside of it.
22 | (with-stdout-to
23 | ;; Output of commands in this sexp will be collected in `magic_dust.ml`.
24 | magic_dust.ml
25 | ;; We use `progn` again to run mulitple commands. The output of
26 | ;; both will go into `magic_dust.ml`.
27 | (progn
28 | ;; The first time we run `pyml_bindgen` to generate the `Hearts`
29 | ;; module.
30 | (run
31 | %{bin:pyml_bindgen}
32 | ./specs/magic_dust/hearts.txt
33 | ;; The python module is in `./py/magic_dust/hearts.py`. See
34 | ;; the README for why we specify the module like this.
35 | magic_dust.hearts
36 | NA
37 | --caml-module
38 | Hearts
39 | --associated-with
40 | module)
41 | ;; We run `pyml_bindgen` again to generate the `Sparkles` module.
42 | ;; Both of these will be in the `Magic_dust` OCaml module.
43 | (run
44 | %{bin:pyml_bindgen}
45 | ./specs/magic_dust/sparkles.txt
46 | magic_dust.sparkles
47 | NA
48 | --caml-module
49 | Sparkles
50 | --associated-with
51 | module)))
52 | ;; Finally we format the `magic_dust.ml` file so it looks nice.
53 | (run ocamlformat magic_dust.ml --inplace))))
54 |
55 | ;; This rule is similar to the previous, though it generates the
56 | ;; `Silly_math` OCaml module.
57 |
58 | (rule
59 | (target silly_math.ml)
60 | (mode
61 | (promote (until-clean)))
62 | (deps
63 | (source_tree specs))
64 | (action
65 | (progn
66 | (with-stdout-to
67 | silly_math.ml
68 | (progn
69 | (run
70 | %{bin:pyml_bindgen}
71 | specs/silly_math/adder/add.txt
72 | silly_math.adder.add
73 | NA
74 | --caml-module
75 | Add
76 | --associated-with
77 | module)
78 | (run
79 | %{bin:pyml_bindgen}
80 | specs/silly_math/subtracter/subtract.txt
81 | silly_math.subtracter.subtract
82 | NA
83 | --caml-module
84 | Subtract
85 | --associated-with
86 | module)))
87 | (run ocamlformat silly_math.ml --inplace))))
88 |
--------------------------------------------------------------------------------
/examples/importing_modules/lib/magic_dust.ml:
--------------------------------------------------------------------------------
1 | module Hearts : sig
2 | type t
3 |
4 | val of_pyobject : Pytypes.pyobject -> t option
5 | val to_pyobject : t -> Pytypes.pyobject
6 | val hearts : unit -> string
7 | end = struct
8 | let filter_opt l = List.filter_map Fun.id l
9 | let py_module = lazy (Py.Import.import_module "magic_dust.hearts")
10 | let import_module () = Lazy.force py_module
11 |
12 | type t = Pytypes.pyobject
13 |
14 | let is_instance pyo =
15 | let py_class = Py.Module.get (import_module ()) "NA" in
16 | Py.Object.is_instance pyo py_class
17 |
18 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
19 | let to_pyobject x = x
20 |
21 | let hearts () =
22 | let callable = Py.Module.get (import_module ()) "hearts" in
23 | let kwargs = filter_opt [] in
24 | Py.String.to_string
25 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
26 | end
27 |
28 | module Sparkles : sig
29 | type t
30 |
31 | val of_pyobject : Pytypes.pyobject -> t option
32 | val to_pyobject : t -> Pytypes.pyobject
33 | val sparkles : unit -> string
34 | end = struct
35 | let filter_opt l = List.filter_map Fun.id l
36 | let py_module = lazy (Py.Import.import_module "magic_dust.sparkles")
37 | let import_module () = Lazy.force py_module
38 |
39 | type t = Pytypes.pyobject
40 |
41 | let is_instance pyo =
42 | let py_class = Py.Module.get (import_module ()) "NA" in
43 | Py.Object.is_instance pyo py_class
44 |
45 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
46 | let to_pyobject x = x
47 |
48 | let sparkles () =
49 | let callable = Py.Module.get (import_module ()) "sparkles" in
50 | let kwargs = filter_opt [] in
51 | Py.String.to_string
52 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
53 | end
54 |
--------------------------------------------------------------------------------
/examples/importing_modules/lib/silly_math.ml:
--------------------------------------------------------------------------------
1 | module Add : sig
2 | type t
3 |
4 | val of_pyobject : Pytypes.pyobject -> t option
5 | val to_pyobject : t -> Pytypes.pyobject
6 | val add : x:int -> y:int -> unit -> int
7 | end = struct
8 | let filter_opt l = List.filter_map Fun.id l
9 | let py_module = lazy (Py.Import.import_module "silly_math.adder.add")
10 | let import_module () = Lazy.force py_module
11 |
12 | type t = Pytypes.pyobject
13 |
14 | let is_instance pyo =
15 | let py_class = Py.Module.get (import_module ()) "NA" in
16 | Py.Object.is_instance pyo py_class
17 |
18 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
19 | let to_pyobject x = x
20 |
21 | let add ~x ~y () =
22 | let callable = Py.Module.get (import_module ()) "add" in
23 | let kwargs =
24 | filter_opt [ Some ("x", Py.Int.of_int x); Some ("y", Py.Int.of_int y) ]
25 | in
26 | Py.Int.to_int @@ Py.Callable.to_function_with_keywords callable [||] kwargs
27 | end
28 |
29 | module Subtract : sig
30 | type t
31 |
32 | val of_pyobject : Pytypes.pyobject -> t option
33 | val to_pyobject : t -> Pytypes.pyobject
34 | val subtract : x:int -> y:int -> unit -> int
35 | end = struct
36 | let filter_opt l = List.filter_map Fun.id l
37 |
38 | let py_module =
39 | lazy (Py.Import.import_module "silly_math.subtracter.subtract")
40 |
41 | let import_module () = Lazy.force py_module
42 |
43 | type t = Pytypes.pyobject
44 |
45 | let is_instance pyo =
46 | let py_class = Py.Module.get (import_module ()) "NA" in
47 | Py.Object.is_instance pyo py_class
48 |
49 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
50 | let to_pyobject x = x
51 |
52 | let subtract ~x ~y () =
53 | let callable = Py.Module.get (import_module ()) "subtract" in
54 | let kwargs =
55 | filter_opt [ Some ("x", Py.Int.of_int x); Some ("y", Py.Int.of_int y) ]
56 | in
57 | Py.Int.to_int @@ Py.Callable.to_function_with_keywords callable [||] kwargs
58 | end
59 |
--------------------------------------------------------------------------------
/examples/importing_modules/lib/specs/magic_dust/hearts.txt:
--------------------------------------------------------------------------------
1 | val hearts : unit -> string
2 |
3 |
--------------------------------------------------------------------------------
/examples/importing_modules/lib/specs/magic_dust/sparkles.txt:
--------------------------------------------------------------------------------
1 | val sparkles : unit -> string
2 |
--------------------------------------------------------------------------------
/examples/importing_modules/lib/specs/silly_math/adder/add.txt:
--------------------------------------------------------------------------------
1 | val add : x:int -> y:int -> unit -> int
2 |
--------------------------------------------------------------------------------
/examples/importing_modules/lib/specs/silly_math/subtracter/subtract.txt:
--------------------------------------------------------------------------------
1 | val subtract : x:int -> y:int -> unit -> int
2 |
--------------------------------------------------------------------------------
/examples/importing_modules/py/magic_dust/hearts.py:
--------------------------------------------------------------------------------
1 | def hearts():
2 | return('hearts...')
3 |
--------------------------------------------------------------------------------
/examples/importing_modules/py/magic_dust/sparkles.py:
--------------------------------------------------------------------------------
1 | def sparkles():
2 | return('sparkle, sparkle!!')
3 |
--------------------------------------------------------------------------------
/examples/importing_modules/py/silly_math/adder/add.py:
--------------------------------------------------------------------------------
1 | def add(x, y):
2 | return x + y
3 |
--------------------------------------------------------------------------------
/examples/importing_modules/py/silly_math/subtracter/subtract.py:
--------------------------------------------------------------------------------
1 | def subtract(x, y):
2 | return x - y
3 |
--------------------------------------------------------------------------------
/examples/importing_modules/run.ml:
--------------------------------------------------------------------------------
1 | open Importing_modules_lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let () = assert (3 = Silly_math.Add.add ~x:1 ~y:2 ())
6 |
7 | let () = assert (-1 = Silly_math.Subtract.subtract ~x:1 ~y:2 ())
8 |
9 | let () = assert ("sparkle, sparkle!!" = Magic_dust.Sparkles.sparkles ())
10 |
11 | let () = assert ("hearts..." = Magic_dust.Hearts.hearts ())
12 |
--------------------------------------------------------------------------------
/examples/importing_modules/test/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps
5 | ../run.exe
6 | (source_tree ../py)))
7 |
--------------------------------------------------------------------------------
/examples/importing_modules/test/run.t:
--------------------------------------------------------------------------------
1 | Run the executable.
2 |
3 | $ PYTHONPATH=$(pwd)/../py ../run.exe
4 |
--------------------------------------------------------------------------------
/examples/quick_start/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/examples/quick_start/README.md:
--------------------------------------------------------------------------------
1 | # Quick Start
2 |
3 | This is a simple example where all files including the Python module that we want to bind are in a single directory.
4 |
5 | For more details about this example, see the [Quick Start](https://github.com/mooreryan/ocaml_python_bindgen#quick-start). There you will see an explanation of how to run `pyml_bindgen` to generate the bindings you see here.
6 |
7 | ## Files
8 |
9 | * `adder.py`: the `Adder` class that we want to bind
10 | * `val_specs.txt`: the OCaml value specifications that define the binding functions
11 | * `lib.ml`: library file generated by running `pyml_bindgen`
12 | * `run.ml`: a runner script for driving the example
13 | * `dune`: the dune file which includes a `rule` for auto-generating the `lib.ml` file
14 |
15 | ## Generating `lib.ml`
16 |
17 | `lib.ml` is the file of `pyml` bindings generated by `pyml_bindgen`.
18 |
19 | The `dune` file has a rule to automatically generate this file for you when you run `dune build` in the root of the repository. Check out the `dune` file to see how to do this...it has lots of comments in there!
20 |
21 | However, if you wanted to make this file manually, you could do so like this:
22 |
23 | ```
24 | $ pyml_bindgen val_specs.txt adder Adder --caml-module Adder | \
25 | ocamlformat --name lib.ml -
26 | ```
27 |
--------------------------------------------------------------------------------
/examples/quick_start/adder.py:
--------------------------------------------------------------------------------
1 | class Adder:
2 | @staticmethod
3 | def add(x, y):
4 | return x + y
5 |
--------------------------------------------------------------------------------
/examples/quick_start/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name run)
5 | (libraries pyml))
6 |
7 | (rule
8 | ;; The file produced by this rule will be called `lib.ml`.
9 | (target lib.ml)
10 | ;; Put the resulting lib.ml file in this directory. When you run
11 | ;; `dune clean`, it will be removed.
12 | (mode
13 | (promote (until-clean)))
14 | ;; Need this expclicitly with dune 3
15 | (deps
16 | (:val_specs val_specs.txt))
17 | (action
18 | ;; `pyml_bindgen` outputs directly to stdout, so we need to redirect
19 | ;; the stdout to `lib.ml`. We do this using `with-stdout-to`.
20 | (with-stdout-to
21 | ;; This is the name of the file to which the output of the
22 | ;; following command will be directed.
23 | lib.ml
24 | ;; This step isn't necessary, but I use `ocamlformat` for
25 | ;; everything, so I would like to have the output of
26 | ;; `pyml_bindgen` also processed by `ocamlformat`. `pipe-stdout`
27 | ;; takes the stdout of the first `run` command and pipes it to the
28 | ;; stdin of the second `run` command.
29 | (pipe-stdout
30 | ;; Here is the acutal `pyml_bindgen` command to generate the
31 | ;; bindings.
32 | (run %{bin:pyml_bindgen} %{val_specs} adder Adder --caml-module Adder)
33 | ;; And finally, process the bindings code with ocamlformat.
34 | (run ocamlformat --name lib.ml -)))))
35 |
--------------------------------------------------------------------------------
/examples/quick_start/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/examples/quick_start/lib.ml:
--------------------------------------------------------------------------------
1 | module Adder : sig
2 | type t
3 |
4 | val of_pyobject : Pytypes.pyobject -> t option
5 | val to_pyobject : t -> Pytypes.pyobject
6 | val add : x:int -> y:int -> unit -> int
7 | end = struct
8 | let filter_opt l = List.filter_map Fun.id l
9 | let py_module = lazy (Py.Import.import_module "adder")
10 | let import_module () = Lazy.force py_module
11 |
12 | type t = Pytypes.pyobject
13 |
14 | let is_instance pyo =
15 | let py_class = Py.Module.get (import_module ()) "Adder" in
16 | Py.Object.is_instance pyo py_class
17 |
18 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
19 | let to_pyobject x = x
20 |
21 | let add ~x ~y () =
22 | let class_ = Py.Module.get (import_module ()) "Adder" in
23 | let callable = Py.Object.find_attr_string class_ "add" in
24 | let kwargs =
25 | filter_opt [ Some ("x", Py.Int.of_int x); Some ("y", Py.Int.of_int y) ]
26 | in
27 | Py.Int.to_int @@ Py.Callable.to_function_with_keywords callable [||] kwargs
28 | end
29 |
--------------------------------------------------------------------------------
/examples/quick_start/run.ml:
--------------------------------------------------------------------------------
1 | open Lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let result = Adder.add ~x:1 ~y:2 ()
6 |
7 | let () = assert (result = 3)
8 |
--------------------------------------------------------------------------------
/examples/quick_start/test/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps
5 | ../run.exe
6 | (source_tree ..)))
7 |
--------------------------------------------------------------------------------
/examples/quick_start/test/run.t:
--------------------------------------------------------------------------------
1 | Run the executable.
2 |
3 | $ PYTHONPATH=$(pwd)/.. ../run.exe
4 |
--------------------------------------------------------------------------------
/examples/quick_start/val_specs.txt:
--------------------------------------------------------------------------------
1 | val add : x:int -> y:int -> unit -> int
2 |
--------------------------------------------------------------------------------
/examples/recursive_modules/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/examples/recursive_modules/README.md:
--------------------------------------------------------------------------------
1 | # Recursive Modules
2 |
3 | When writing bindings for Python libraries, you will need to bind cyclic Python classes, or, Python classes that reference each other. Here is an example.
4 |
5 | ```python
6 | class Cat:
7 | # Cats have humans
8 | def __init__(self, name):
9 | self.name = name
10 | self.human = None
11 |
12 | def __str__(self):
13 | if self.human:
14 | human = self.human.name
15 | else:
16 | human = "none"
17 |
18 | return f'Cat -- name: {self.name}, human: {human}'
19 |
20 | def adopt_human(self, human):
21 | self.human = human
22 |
23 | class Human:
24 | # Humans have cats
25 | def __init__(self, name):
26 | self.name = name
27 | self.cat = None
28 |
29 | def __str__(self):
30 | if self.cat:
31 | cat = self.cat.name
32 | else:
33 | cat = "none"
34 |
35 | return f'Human -- name: {self.name}, cat: {cat}'
36 |
37 | def adopt_cat(self, cat):
38 | self.cat = cat
39 | ```
40 |
41 | You can see that the `Cat` and `Human` classes make reference to one another. This can be a problem when writing OCaml bindings.
42 |
43 | One solution is to use [recursive modules](https://ocaml.org/manual/recursivemodules.html). Rather than run `pyml_bindgen` separately for each class we want to bind, and then manually [modify](https://mooreryan.github.io/ocaml_python_bindgen/recursive/) the results to make the modules recursive, this directory shows you how you can use the helper scripts `gen_multi` and `combine_rec_modules` to automate this task.
44 |
45 | ## `gen_multi`
46 |
47 | `gen_multi` is a wrapper for running `pyml_bindgen` multiples times and combining it's output into a single file.
48 |
49 | It takes a tab-separated file (TSV), where each line describes the command line options for running `pyml_bindgen` on a specific specs file.
50 |
51 | Here is what the input file `cli_specs.tsv` looks like from this example.
52 |
53 | | signatures | py_module | py_class | associated_with | caml_module | split_caml_module | embed_python_source | of_pyo_ret_type |
54 | |----------------------|-----------|----------|-----------------|-------------|-------------------|---------------------|-----------------|
55 | | specs/human_spec.txt | human | Human | class | Human | NA | ../py/human.py | no_check |
56 | | specs/cat_spec.txt | cat | Cat | class | Cat | NA | ../py/cat.py | no_check |
57 |
58 | The first row must be given in this exact order. Each column is one of the command line options to `pyml_bindgen`. You can put `NA` (or `na`, or blank) in cases in which you would not pass the flag/option to `pyml_bindgen`.
59 |
60 | One potentially tricky thing is that the file paths will be with respect to the location in which the `gen_multi` program is run from, rather than the location of the `cli_specs.tsv` file. If it is giving you trouble, you could use absolute paths instead.
61 |
62 | ## `combine_rec_modules`
63 |
64 | This is a simple script that takes the output of say, `gen_multi` and turns all of the modules into recursive modules.
65 |
66 | Something like this
67 |
68 | ```ocaml
69 | module A : sig
70 | ...
71 | end = struct
72 | ...
73 | end
74 |
75 | module B : sig
76 | ...
77 | end = struct
78 | ...
79 | end
80 |
81 | module C : sig
82 | ...
83 | end = struct
84 | ...
85 | end
86 | ```
87 |
88 | would become something like this:
89 |
90 | ```ocaml
91 | module rec A : sig
92 | ...
93 | end = struct
94 | ...
95 | end
96 |
97 | and B : sig
98 | ...
99 | end = struct
100 | ...
101 | end
102 |
103 | and C : sig
104 | ...
105 | end = struct
106 | ...
107 | end
108 | ```
109 |
110 | ## Generating the modules
111 |
112 | See `lib/dune` for an automatic way to do this. But basically, it goes something like this:
113 |
114 | ```bash
115 | $ gen_multi ./specs/cli_specs.tsv \
116 | | combine_rec_modules /dev/stdin \
117 | | ocamlformat --name a.ml - \
118 | > lib.ml
119 | ```
120 |
--------------------------------------------------------------------------------
/examples/recursive_modules/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name run)
5 | (libraries base pyml stdio examples_recursive_modules_lib))
6 |
--------------------------------------------------------------------------------
/examples/recursive_modules/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/examples/recursive_modules/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name examples_recursive_modules_lib)
5 | (libraries pyml))
6 |
7 | (rule
8 | (target lib.ml)
9 | (mode
10 | (promote (until-clean)))
11 | (deps
12 | (source_tree specs)
13 | (source_tree ../py))
14 | (action
15 | (with-stdout-to
16 | lib.ml
17 | (pipe-stdout
18 | (run %{bin:gen_multi} ./specs/cli_specs.tsv)
19 | (run %{bin:combine_rec_modules} /dev/stdin)
20 | (run ocamlformat --name a.ml -)))))
21 |
--------------------------------------------------------------------------------
/examples/recursive_modules/lib/lib.ml:
--------------------------------------------------------------------------------
1 | module rec Human : sig
2 | type t
3 |
4 | val of_pyobject : Pytypes.pyobject -> t
5 | val to_pyobject : t -> Pytypes.pyobject
6 | val create : name:string -> unit -> t
7 | val to_string : t -> unit -> string
8 | val adopt_cat : t -> cat:Cat.t -> unit -> unit
9 | val name : t -> string
10 | val cat : t -> Cat.t
11 | end = struct
12 | let filter_opt l = List.filter_map Fun.id l
13 |
14 | let py_module =
15 | lazy
16 | (let source =
17 | {pyml_bindgen_string_literal|class Human:
18 | # Humans have cats
19 | def __init__(self, name):
20 | self.name = name
21 | self.cat = None
22 |
23 | def __str__(self):
24 | if self.cat:
25 | cat = self.cat.name
26 | else:
27 | cat = "none"
28 |
29 | return f'Human -- name: {self.name}, cat: {cat}'
30 |
31 | def adopt_cat(self, cat):
32 | self.cat = cat
33 | |pyml_bindgen_string_literal}
34 | in
35 | let filename =
36 | {pyml_bindgen_string_literal|../py/human.py|pyml_bindgen_string_literal}
37 | in
38 | let bytecode = Py.compile ~filename ~source `Exec in
39 | Py.Import.exec_code_module
40 | {pyml_bindgen_string_literal|human|pyml_bindgen_string_literal}
41 | bytecode)
42 |
43 | let import_module () = Lazy.force py_module
44 |
45 | type t = Pytypes.pyobject
46 |
47 | let of_pyobject pyo = pyo
48 | let to_pyobject x = x
49 |
50 | let create ~name () =
51 | let callable = Py.Module.get (import_module ()) "Human" in
52 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
53 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
54 |
55 | let to_string t () =
56 | let callable = Py.Object.find_attr_string t "__str__" in
57 | let kwargs = filter_opt [] in
58 | Py.String.to_string
59 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
60 |
61 | let adopt_cat t ~cat () =
62 | let callable = Py.Object.find_attr_string t "adopt_cat" in
63 | let kwargs = filter_opt [ Some ("cat", Cat.to_pyobject cat) ] in
64 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
65 |
66 | let name t = Py.String.to_string @@ Py.Object.find_attr_string t "name"
67 | let cat t = Cat.of_pyobject @@ Py.Object.find_attr_string t "cat"
68 | end
69 |
70 | and Cat : sig
71 | type t
72 |
73 | val of_pyobject : Pytypes.pyobject -> t
74 | val to_pyobject : t -> Pytypes.pyobject
75 | val create : name:string -> unit -> t
76 | val to_string : t -> unit -> string
77 | val adopt_human : t -> human:Human.t -> unit -> unit
78 | val name : t -> string
79 | val human : t -> Human.t
80 | end = struct
81 | let filter_opt l = List.filter_map Fun.id l
82 |
83 | let py_module =
84 | lazy
85 | (let source =
86 | {pyml_bindgen_string_literal|class Cat:
87 | # Cats have humans
88 | def __init__(self, name):
89 | self.name = name
90 | self.human = None
91 |
92 | def __str__(self):
93 | if self.human:
94 | human = self.human.name
95 | else:
96 | human = "none"
97 |
98 | return f'Cat -- name: {self.name}, human: {human}'
99 |
100 | def adopt_human(self, human):
101 | self.human = human
102 | |pyml_bindgen_string_literal}
103 | in
104 | let filename =
105 | {pyml_bindgen_string_literal|../py/cat.py|pyml_bindgen_string_literal}
106 | in
107 | let bytecode = Py.compile ~filename ~source `Exec in
108 | Py.Import.exec_code_module
109 | {pyml_bindgen_string_literal|cat|pyml_bindgen_string_literal} bytecode)
110 |
111 | let import_module () = Lazy.force py_module
112 |
113 | type t = Pytypes.pyobject
114 |
115 | let of_pyobject pyo = pyo
116 | let to_pyobject x = x
117 |
118 | let create ~name () =
119 | let callable = Py.Module.get (import_module ()) "Cat" in
120 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
121 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
122 |
123 | let to_string t () =
124 | let callable = Py.Object.find_attr_string t "__str__" in
125 | let kwargs = filter_opt [] in
126 | Py.String.to_string
127 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
128 |
129 | let adopt_human t ~human () =
130 | let callable = Py.Object.find_attr_string t "adopt_human" in
131 | let kwargs = filter_opt [ Some ("human", Human.to_pyobject human) ] in
132 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
133 |
134 | let name t = Py.String.to_string @@ Py.Object.find_attr_string t "name"
135 | let human t = Human.of_pyobject @@ Py.Object.find_attr_string t "human"
136 | end
137 |
--------------------------------------------------------------------------------
/examples/recursive_modules/lib/specs/cat_spec.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t
2 | [@@py_fun_name __init__]
3 |
4 | val to_string : t -> unit -> string
5 | [@@py_fun_name __str__]
6 |
7 | val adopt_human : t -> human:Human.t -> unit -> unit
8 |
9 | val name : t -> string
10 | val human : t -> Human.t
--------------------------------------------------------------------------------
/examples/recursive_modules/lib/specs/cli_specs.tsv:
--------------------------------------------------------------------------------
1 | signatures py_module py_class associated_with caml_module split_caml_module embed_python_source of_pyo_ret_type
2 | specs/human_spec.txt human Human class Human NA ../py/human.py no_check
3 | specs/cat_spec.txt cat Cat class Cat NA ../py/cat.py no_check
4 |
--------------------------------------------------------------------------------
/examples/recursive_modules/lib/specs/human_spec.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t
2 | [@@py_fun_name __init__]
3 |
4 | val to_string : t -> unit -> string
5 | [@@py_fun_name __str__]
6 |
7 | val adopt_cat : t -> cat:Cat.t -> unit -> unit
8 |
9 | val name : t -> string
10 | val cat : t -> Cat.t
--------------------------------------------------------------------------------
/examples/recursive_modules/py/cat.py:
--------------------------------------------------------------------------------
1 | class Cat:
2 | # Cats have humans
3 | def __init__(self, name):
4 | self.name = name
5 | self.human = None
6 |
7 | def __str__(self):
8 | if self.human:
9 | human = self.human.name
10 | else:
11 | human = "none"
12 |
13 | return f'Cat -- name: {self.name}, human: {human}'
14 |
15 | def adopt_human(self, human):
16 | self.human = human
17 |
--------------------------------------------------------------------------------
/examples/recursive_modules/py/human.py:
--------------------------------------------------------------------------------
1 | class Human:
2 | # Humans have cats
3 | def __init__(self, name):
4 | self.name = name
5 | self.cat = None
6 |
7 | def __str__(self):
8 | if self.cat:
9 | cat = self.cat.name
10 | else:
11 | cat = "none"
12 |
13 | return f'Human -- name: {self.name}, cat: {cat}'
14 |
15 | def adopt_cat(self, cat):
16 | self.cat = cat
17 |
--------------------------------------------------------------------------------
/examples/recursive_modules/run.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Examples_recursive_modules_lib.Lib
3 |
4 | let () = Py.initialize ()
5 |
6 | let print_endline = Stdio.print_endline
7 |
8 | let human = Human.create ~name:"Bob" ()
9 |
10 | let cat = Cat.create ~name:"Apple" ()
11 |
12 | let () =
13 | print_endline "Before adoption...";
14 | print_endline @@ Human.to_string human ();
15 | print_endline @@ Cat.to_string cat ()
16 |
17 | let () =
18 | Human.adopt_cat human ~cat ();
19 | Cat.adopt_human cat ~human ()
20 |
21 | let () =
22 | print_endline "After adoption...";
23 | print_endline @@ Human.to_string human ();
24 | print_endline @@ Cat.to_string cat ()
25 |
--------------------------------------------------------------------------------
/examples/recursive_modules/test/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps ../run.exe))
5 |
--------------------------------------------------------------------------------
/examples/recursive_modules/test/run.t:
--------------------------------------------------------------------------------
1 | Run the executable.
2 |
3 | $ ../run.exe
4 | Before adoption...
5 | Human -- name: Bob, cat: none
6 | Cat -- name: Apple, human: none
7 | After adoption...
8 | Human -- name: Bob, cat: Apple
9 | Cat -- name: Apple, human: Bob
10 |
--------------------------------------------------------------------------------
/examples/tuples/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/examples/tuples/README.md:
--------------------------------------------------------------------------------
1 | # Tuples
2 |
3 | - They can be passed in as arguments, or returned from functions.
4 | - Only basic types and python objects can occur in tuples
5 | - No nesting
6 | - No options
7 | - You _can_ stick tuples inside a `List`, `Seq`, or `Array`, e.g., `(int * string) list`, but _not_ in `option` or `Or_error.t`.
8 | - If you break these rules, you will get runtime errors :)
9 |
10 | ## Generate bindings
11 |
12 | See the `dune` file for auto-generating the bindings. If you don't want to use Dune rules for this, you can still use the rule as an example of how to run `pyml_bindgen` manually.
13 |
--------------------------------------------------------------------------------
/examples/tuples/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (enabled_if
3 | (= %{profile} dev))
4 | (name run)
5 | (libraries pyml))
6 |
7 | (rule
8 | ;; The file produced by this rule will be called `lib.ml`.
9 | (target lib.ml)
10 | ;; Put the resulting lib.ml file in this directory. When you run
11 | ;; `dune clean`, it will be removed.
12 | (mode
13 | (promote (until-clean)))
14 | ;; Need this expclicitly with dune 3
15 | (deps
16 | (:val_specs val_specs.txt))
17 | (action
18 | ;; `pyml_bindgen` outputs directly to stdout, so we need to redirect
19 | ;; the stdout to `lib.ml`. We do this using `with-stdout-to`.
20 | (with-stdout-to
21 | ;; This is the name of the file to which the output of the
22 | ;; following command will be directed.
23 | lib.ml
24 | ;; This step isn't necessary, but I use `ocamlformat` for
25 | ;; everything, so I would like to have the output of
26 | ;; `pyml_bindgen` also processed by `ocamlformat`. `pipe-stdout`
27 | ;; takes the stdout of the first `run` command and pipes it to the
28 | ;; stdin of the second `run` command.
29 | (pipe-stdout
30 | ;; Here is the acutal `pyml_bindgen` command to generate the
31 | ;; bindings.
32 | (run
33 | %{bin:pyml_bindgen}
34 | %{val_specs}
35 | tuples
36 | NA
37 | --associated-with
38 | module
39 | --caml-module
40 | Tuples)
41 | ;; And finally, process the bindings code with ocamlformat.
42 | (run ocamlformat --name lib.ml -)))))
43 |
--------------------------------------------------------------------------------
/examples/tuples/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/examples/tuples/run.ml:
--------------------------------------------------------------------------------
1 | open Lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let () = assert (Tuples.pair ~x:1 ~y:"apple" () = (1, "apple"))
6 |
7 | let () = assert (Tuples.first ~x:(1, 2) () = 1)
8 |
9 | let () = assert (Tuples.identity ~x:(1, 2) () = (1, 2))
10 |
11 | let () = assert (Tuples.make () = (0, 0))
12 |
13 | let () = assert (Tuples.apple ~x:[ 1; 2 ] () = [ 1; 2 ])
14 |
15 | let () = assert (Tuples.pie_list ~x:[ (1, 2); (3, 4) ] () = [ (1, 2); (3, 4) ])
16 |
17 | let () =
18 | assert (Tuples.pie_array ~x:[| (1, 2); (3, 4) |] () = [| (1, 2); (3, 4) |])
19 |
20 | let () =
21 | let l = [ (1, 2); (3, 4) ] in
22 | let x = List.to_seq l in
23 | let result = Tuples.pie_seq ~x () in
24 | assert (List.of_seq result = l)
25 |
26 | let () =
27 | let x = (1, "1") in
28 | assert (Tuples.t2 ~x () = x)
29 |
30 | let () =
31 | let x = (1, "1", 1.0) in
32 | assert (Tuples.t3 ~x () = x)
33 |
34 | let () =
35 | let x = (1, "1", 1.0, true) in
36 | assert (Tuples.t4 ~x () = x)
37 |
38 | let () =
39 | let x = (1, "1", 1.0, true, 1) in
40 | assert (Tuples.t5 ~x () = x)
41 |
42 | let () =
43 | let x = [ (1, "1", 1.0, true, 1) ] in
44 | assert (Tuples.t5_list ~x () = x)
45 |
46 | let x = 1
47 |
48 | let y = 2
49 |
50 | let py_tup = (Py.Int.of_int x, Py.Int.of_int y)
51 |
52 | let () =
53 | let x', y' = Tuples.t2_pyobject ~x:py_tup () in
54 | let x', y' = (Py.Int.to_int x', Py.Int.to_int y') in
55 | assert ((x, y) = (x', y'))
56 |
57 | let () =
58 | let x', y' = Tuples.t2_pyobject2 ~x:py_tup () in
59 | let x', y' = (Py.Int.to_int x', Py.Int.to_int y') in
60 | assert ((x, y) = (x', y'))
61 |
62 | let () =
63 | let x', y' =
64 | match Tuples.t2_pyobject_list ~x:[ py_tup ] () with
65 | | [ a ] -> a
66 | | _ -> assert false
67 | in
68 | let x', y' = (Py.Int.to_int x', Py.Int.to_int y') in
69 | assert ((x, y) = (x', y'))
70 |
71 | let () =
72 | let x', y' =
73 | match Tuples.t2_pyobject2_list ~x:[ py_tup ] () with
74 | | [ a ] -> a
75 | | _ -> assert false
76 | in
77 | let x', y' = (Py.Int.to_int x', Py.Int.to_int y') in
78 | assert ((x, y) = (x', y'))
79 |
80 | let () =
81 | let points1 = [ (1, 2); (3, 4) ] in
82 | let points2 = [ (10, 20); (30, 40) ] in
83 | let actual = Tuples.add ~points1 ~points2 () in
84 | let expected = [ (11, 22); (33, 44) ] in
85 | assert (actual = expected)
86 |
--------------------------------------------------------------------------------
/examples/tuples/test/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps
5 | ../run.exe
6 | (source_tree ..)))
7 |
--------------------------------------------------------------------------------
/examples/tuples/test/run.t:
--------------------------------------------------------------------------------
1 | Run the executable.
2 |
3 | $ PYTHONPATH=$(pwd)/.. ../run.exe
4 |
--------------------------------------------------------------------------------
/examples/tuples/tuples.py:
--------------------------------------------------------------------------------
1 | def pair(x, y):
2 | return (x, y)
3 |
4 |
5 | def first(x):
6 | return x[0]
7 |
8 |
9 | def identity(x):
10 | return x
11 |
12 |
13 | def make(x=(0, 0)):
14 | return x
15 |
16 |
17 | def add(points1, points2):
18 | return [(x1 + y1, x2 + y2) for (x1, x2), (y1, y2) in zip(points1, points2)]
19 |
--------------------------------------------------------------------------------
/examples/tuples/val_specs.txt:
--------------------------------------------------------------------------------
1 | val pair : x:int -> y:string -> unit -> int * string
2 | val identity : x:int * int -> unit -> int * int
3 | val first : x:int * int -> unit -> int
4 | val make : ?x:int * int -> unit -> int * int
5 | val apple : x:int list -> unit -> int list
6 | [@@py_fun_name identity]
7 |
8 | val pie_list : x:(int * int) list -> unit -> (int * int) list
9 | [@@py_fun_name identity]
10 |
11 | val pie_array : x:(int * int) array -> unit -> (int * int) array
12 | [@@py_fun_name identity]
13 |
14 | val pie_seq : x:(int * int) Seq.t -> unit -> (int * int) Seq.t
15 | [@@py_fun_name identity]
16 |
17 | val t2 : x:int * string -> unit -> int * string
18 | [@@py_fun_name identity]
19 |
20 | val t3 : x:int * string * float -> unit -> int * string * float
21 | [@@py_fun_name identity]
22 |
23 | val t4 : x:int * string * float * bool -> unit -> int * string * float * bool
24 | [@@py_fun_name identity]
25 |
26 | val t5 : x:int * string * float * bool * int -> unit -> int * string * float * bool * int
27 | [@@py_fun_name identity]
28 |
29 | val t5_list : x:(int * string * float * bool * int) list -> unit -> (int * string * float * bool * int) list
30 | [@@py_fun_name identity]
31 |
32 | val t2_pyobject : x:Py.Object.t * Pytypes.pyobject -> unit -> Py.Object.t * Pytypes.pyobject
33 | [@@py_fun_name identity]
34 |
35 | val t2_pyobject2 : x:Pytypes.pyobject * Py.Object.t -> unit -> Pytypes.pyobject * Py.Object.t
36 | [@@py_fun_name identity]
37 |
38 | val t2_pyobject_list : x:(Py.Object.t * Pytypes.pyobject) list -> unit -> (Py.Object.t * Pytypes.pyobject) list
39 | [@@py_fun_name identity]
40 |
41 | val t2_pyobject2_list : x:(Pytypes.pyobject * Py.Object.t) list -> unit -> (Pytypes.pyobject * Py.Object.t) list
42 | [@@py_fun_name identity]
43 |
44 | val add : points1:(int * int) list -> points2:(int * int) list -> unit -> (int * int) list
45 |
46 | # NOT ALLOWED because only basic types can be in tuples.
47 | # val weird : x:int -> unit -> int * int list
48 |
--------------------------------------------------------------------------------
/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name lib)
3 | (libraries angstrom base re stdio unix)
4 | (instrumentation
5 | (backend bisect_ppx))
6 | (preprocess
7 | (pps ppx_let ppx_sexp_conv ppx_string)))
8 |
--------------------------------------------------------------------------------
/lib/mkdir.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | type file_perm = int [@@deriving of_sexp]
4 |
5 | module Mkdir : sig
6 | val mkdir : ?perm:file_perm -> string -> unit
7 |
8 | val mkdir_p : ?perm:file_perm -> string -> unit
9 | end = struct
10 | let atom x = Sexp.Atom x
11 |
12 | let list x = Sexp.List x
13 |
14 | let record l =
15 | list (List.map l ~f:(fun (name, value) -> list [ atom name; value ]))
16 |
17 | (* This wrapper improves the content of the Unix_error exception raised by the
18 | standard library (by including a sexp of the function arguments), and it
19 | optionally restarts syscalls on EINTR. *)
20 | let improve f make_arg_sexps =
21 | try f ()
22 | with Unix.Unix_error (e, s, _) ->
23 | let buf = Buffer.create 100 in
24 | let fmt = Caml.Format.formatter_of_buffer buf in
25 | Caml.Format.pp_set_margin fmt 10000;
26 | Sexp.pp_hum fmt (record (make_arg_sexps ()));
27 | Caml.Format.pp_print_flush fmt ();
28 | let arg_str = Buffer.contents buf in
29 | raise (Unix.Unix_error (e, s, arg_str))
30 |
31 | let dirname_r filename = ("dirname", atom filename)
32 |
33 | let file_perm_r perm = ("perm", atom (Printf.sprintf "0o%o" perm))
34 |
35 | let[@inline always] improve_mkdir mkdir dirname perm =
36 | improve
37 | (fun () -> mkdir dirname perm)
38 | (fun () -> [ dirname_r dirname; file_perm_r perm ])
39 |
40 | let mkdir = improve_mkdir Unix.mkdir
41 |
42 | let mkdir_idempotent dirname perm =
43 | match Unix.mkdir dirname perm with
44 | | () -> ()
45 | (* [mkdir] on MacOSX returns [EISDIR] instead of [EEXIST] if the directory
46 | already exists. *)
47 | | exception Unix.Unix_error ((EEXIST | EISDIR), _, _) -> ()
48 |
49 | let mkdir_idempotent = improve_mkdir mkdir_idempotent
50 |
51 | let rec mkdir_p dir perm =
52 | match mkdir_idempotent dir perm with
53 | | () -> ()
54 | | exception (Unix.Unix_error (ENOENT, _, _) as exn) ->
55 | let parent = Caml.Filename.dirname dir in
56 | if String.( = ) parent dir then raise exn
57 | else (
58 | mkdir_p parent perm;
59 | mkdir_idempotent dir perm)
60 |
61 | let mkdir ?(perm = 0o750) dir = mkdir dir perm
62 |
63 | let mkdir_p ?(perm = 0o750) dir = mkdir_p dir perm
64 | end
65 |
66 | include Mkdir
67 |
68 | (* Apapted from JaneStreet Core_unix. Original license follows. *)
69 | (* The MIT License
70 |
71 | Copyright (c) 2008--2022 Jane Street Group, LLC opensource@janestreet.com
72 |
73 | Permission is hereby granted, free of charge, to any person obtaining a copy
74 | of this software and associated documentation files (the "Software"), to deal
75 | in the Software without restriction, including without limitation the rights
76 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
77 | copies of the Software, and to permit persons to whom the Software is
78 | furnished to do so, subject to the following conditions:
79 |
80 | The above copyright notice and this permission notice shall be included in
81 | all copies or substantial portions of the Software.
82 |
83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
84 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
85 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
86 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
87 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
88 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
89 | SOFTWARE. *)
90 |
--------------------------------------------------------------------------------
/lib/shared.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | (** Note, this one isn't included in the "all" functions below. *)
4 | let gen_import_module_impl ?python_source py_module =
5 | match python_source with
6 | | None ->
7 | [%string
8 | "let py_module = lazy (Py.Import.import_module \"%{py_module}\")\n\
9 | let import_module () = Lazy.force py_module"]
10 | | Some file_name ->
11 | let source = Utils.read_python_source file_name in
12 | [%string
13 | "let py_module = lazy (\n\
14 | \ let source = \
15 | {pyml_bindgen_string_literal|%{source}|pyml_bindgen_string_literal} in\n\
16 | \ let filename = \
17 | {pyml_bindgen_string_literal|%{file_name}|pyml_bindgen_string_literal} \
18 | in\n\
19 | \ let bytecode = Py.compile ~filename ~source `Exec in\n\
20 | \ Py.Import.exec_code_module \
21 | {pyml_bindgen_string_literal|%{py_module}|pyml_bindgen_string_literal} \
22 | bytecode)\n\
23 | let import_module () = Lazy.force py_module"]
24 |
25 | let gen_type_sig () = "type t"
26 |
27 | let gen_type_impl () = "type t = Pytypes.pyobject"
28 |
29 | let gen_of_pyobject_sig = function
30 | | `No_check -> "val of_pyobject : Pytypes.pyobject -> t"
31 | | `Option -> "val of_pyobject : Pytypes.pyobject -> t option"
32 | | `Or_error -> "val of_pyobject : Pytypes.pyobject -> t Or_error.t"
33 |
34 | (** Only generates stuff for simple python types, or for custom classes. If you
35 | have a list you will need to write your own. *)
36 | let gen_of_pyobject_impl of_pyo_return_type of_pyo_otype =
37 | match (of_pyo_return_type, of_pyo_otype) with
38 | | `Option, `Custom py_class ->
39 | [%string
40 | {|
41 | let is_instance pyo =
42 | let py_class = Py.Module.get (import_module ()) "%{py_class}" in
43 | Py.Object.is_instance pyo py_class
44 |
45 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
46 | |}]
47 | | `Or_error, `Custom py_class ->
48 | [%string
49 | {|
50 | let is_instance pyo =
51 | let py_class = Py.Module.get (import_module ()) "%{py_class}" in
52 | Py.Object.is_instance pyo py_class
53 |
54 | let of_pyobject pyo =
55 | if is_instance pyo then Or_error.return pyo
56 | else Or_error.error_string "Expected %{py_class}"
57 | |}]
58 | | `Option, `Int ->
59 | [%string
60 | {| let of_pyobject pyo = if Py.Int.check pyo then Some pyo else None |}]
61 | | `Or_error, `Int ->
62 | [%string
63 | {|
64 | let of_pyobject pyo =
65 | if Py.Int.check pyo then Or_error.return
66 | else Or_error.error_string "Expected Int"
67 | |}]
68 | | `Option, `Float ->
69 | [%string
70 | {| let of_pyobject pyo = if Py.Float.check pyo then Some pyo else None |}]
71 | | `Or_error, `Float ->
72 | [%string
73 | {|
74 | let of_pyobject pyo =
75 | if Py.Float.check pyo then Or_error.return pyo
76 | else Or_error.error_string "Expected Float"
77 | |}]
78 | | `Option, `String ->
79 | [%string
80 | {| let of_pyobject pyo = if Py.String.check pyo then Some pyo else None |}]
81 | | `Or_error, `String ->
82 | [%string
83 | {|
84 | let of_pyobject pyo =
85 | if Py.String.check pyo then Or_error.return pyo
86 | else Or_error.error_string "Expected String"
87 | |}]
88 | | `Option, `Bool ->
89 | [%string
90 | {| let of_pyobject pyo = if Py.Bool.check pyo then Some pyo else None |}]
91 | | `Or_error, `Bool ->
92 | [%string
93 | {|
94 | let of_pyobject pyo =
95 | if Py.Bool.check pyo then Or_error.return pyo
96 | else Or_error.error_string "Expected Bool"
97 | |}]
98 | | `No_check, `Custom _
99 | | `No_check, `Int
100 | | `No_check, `Float
101 | | `No_check, `String
102 | | `No_check, `Bool ->
103 | "let of_pyobject pyo = pyo"
104 |
105 | let gen_to_pyobject_sig () = "val to_pyobject : t -> Pytypes.pyobject"
106 |
107 | let gen_to_pyobject_impl () = "let to_pyobject x = x"
108 |
109 | let gen_all_signatures of_pyo_return_type =
110 | [
111 | gen_type_sig ();
112 | gen_of_pyobject_sig of_pyo_return_type;
113 | gen_to_pyobject_sig ();
114 | ]
115 |
116 | let gen_all_functions of_pyo_return_type of_pyo_otype =
117 | [
118 | gen_type_impl ();
119 | gen_of_pyobject_impl of_pyo_return_type of_pyo_otype;
120 | gen_to_pyobject_impl ();
121 | ]
122 |
--------------------------------------------------------------------------------
/lib/specs_file.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | (* TODO ideally we would integrate this parsing step with our other parsers and
4 | then have a single "specs file" parser. *)
5 |
6 | type spec = { attrs : string option; val_spec : string } [@@deriving sexp]
7 |
8 | let is_comment_line s = String.is_prefix ~prefix:"#" s
9 |
10 | let is_val_start s = String.is_prefix ~prefix:"val" s
11 |
12 | let all_whitespace = Re.compile @@ Re.Perl.re "^\\s*$"
13 |
14 | let is_all_whitespace s = Re.execp all_whitespace s
15 |
16 | let comment_marker = Re.compile @@ Re.Perl.re "^#\\s*"
17 |
18 | let cat s1 s2 = s1 ^ " " ^ s2
19 |
20 | (* TODO should we include 0-9 here? *)
21 | (* TODO We are being more restrictive than normal in that each attr must be on
22 | its own line, and only one per line. *)
23 | let attribute_line =
24 | Re.compile @@ Re.Perl.re "^\\s*\\[@@[a-zA-Z_]+\\s+[a-zA-Z_ ]+\\]\\s*$"
25 |
26 | let attributes_not_at_start =
27 | Re.compile @@ Re.Perl.re "^\\S+.*\\[@@[a-zA-Z_]+\\s+[a-zA-Z_ ]+\\]"
28 |
29 | let has_attributes_not_at_start line = Re.execp attributes_not_at_start line
30 |
31 | let is_attribute_line line = Re.execp attribute_line line
32 |
33 | (* assert false cases should be impossible unless I made a mistake. failwith
34 | cases can happen with bad user input in the file. *)
35 | let read fname =
36 | let open Stdio in
37 | let lines =
38 | List.filter ~f:(fun l ->
39 | (not (is_all_whitespace l)) && not (is_comment_line l))
40 | @@ In_channel.read_lines fname
41 | in
42 | let current_attr, current_val_spec, specs =
43 | List.fold lines ~init:(None, None, [])
44 | ~f:(fun (attrs, val_spec, all) line ->
45 | let line = String.strip line in
46 | if has_attributes_not_at_start line then
47 | failwith "attributes must start a line";
48 | match (is_attribute_line line, is_val_start line, attrs, val_spec) with
49 | | true, true, None, None
50 | | true, true, None, Some _
51 | | true, true, Some _, None
52 | | true, true, Some _, Some _ ->
53 | assert false
54 | (* In an attribute line *)
55 | | true, false, None, None ->
56 | failwith "We have attributes but no val spec for them to go with."
57 | | true, false, Some _, None -> assert false
58 | | true, false, None, Some current_val_spec ->
59 | let new_attrs = line in
60 | (Some new_attrs, Some current_val_spec, all)
61 | | true, false, Some current_attrs, Some current_val_spec ->
62 | let new_attrs = line in
63 | (Some (cat current_attrs new_attrs), Some current_val_spec, all)
64 | (* Starting a new val spec *)
65 | | false, true, None, None ->
66 | let new_val_spec = line in
67 | (None, Some new_val_spec, all)
68 | | false, true, None, Some current_val_spec ->
69 | (* Track the old val spec. *)
70 | let all = { attrs = None; val_spec = current_val_spec } :: all in
71 | (* Set up the new one. *)
72 | let new_val_spec = line in
73 | (None, Some new_val_spec, all)
74 | | false, true, Some _, None -> assert false
75 | | false, true, Some current_attrs, Some current_val_spec ->
76 | (* Track the old val spec. *)
77 | let all =
78 | { attrs = Some current_attrs; val_spec = current_val_spec } :: all
79 | in
80 | (* Set up the new one. *)
81 | let new_val_spec = line in
82 | (None, Some new_val_spec, all)
83 | (* In the middle of a val spec *)
84 | | false, false, None, None ->
85 | failwith "In the middle of a val spec, but have none to work on."
86 | | false, false, None, Some current_val_spec ->
87 | let new_val_spec = line in
88 | (None, Some (cat current_val_spec new_val_spec), all)
89 | | false, false, Some _, None -> assert false
90 | | false, false, Some _, Some _ ->
91 | failwith "Found unused attrs but in the middle of a val spec.")
92 | in
93 | (* Finish off last spec if it is there. *)
94 | let specs =
95 | match (current_attr, current_val_spec) with
96 | | None, None -> specs
97 | | None, Some val_spec -> { attrs = None; val_spec } :: specs
98 | | Some _, None ->
99 | prerr_endline
100 | "WARNING: currently in a attributes line but hit EOF without getting \
101 | a val_spec to go with it.";
102 | specs
103 | | Some attrs, Some val_spec -> { attrs = Some attrs; val_spec } :: specs
104 | in
105 | List.rev specs
106 |
--------------------------------------------------------------------------------
/lib/utils.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 |
3 | let is_space = function ' ' -> true | _ -> false
4 |
5 | let is_capital_letter = function 'A' .. 'Z' -> true | _ -> false
6 |
7 | let is_lowercase_letter = function 'a' .. 'z' -> true | _ -> false
8 |
9 | let is_ok_for_name = function
10 | | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' -> true
11 | | _ -> false
12 |
13 | let read_python_source file_name = Stdio.In_channel.read_all file_name
14 |
15 | module Angstrom_helpers = struct
16 | open Angstrom
17 |
18 | let is_whitepace = function ' ' | '\t' -> true | _ -> false
19 |
20 | let is_whitespace_or_eol = function
21 | | ' ' | '\t' | '\r' | '\n' -> true
22 | | _ -> false
23 |
24 | let eoi =
25 | let open Angstrom.Let_syntax in
26 | end_of_input
27 | <|> let%bind bad_thing = take_till is_whitespace_or_eol in
28 | fail bad_thing
29 | > "parser failed before all input was consumed at token"
30 | end
31 |
32 | let spaces = Re.compile @@ Re.Perl.re "[ \n]+"
33 |
34 | let squash_spaces s = Re.replace_string spaces s ~by:" "
35 |
36 | let clean s = String.strip @@ squash_spaces s
37 |
38 | let todo_type = "type 'a todo = unit -> 'a"
39 |
40 | let not_implemented_type = "type 'a not_implemented = unit -> 'a"
41 |
42 | let or_error_re = Re.compile @@ Re.Perl.re "Or_error\\.t"
43 |
44 | let todo_re = Re.compile @@ Re.Perl.re "'a todo"
45 |
46 | let not_implemented_re = Re.compile @@ Re.Perl.re "'a not_implemented"
47 |
48 | (* TODO move all these check needs functions up into the one in pyml_bindgen
49 | main. *)
50 |
51 | (* This would give false positives if the Or_error is in something other than
52 | the return type. Although, other functions should prevent valid val_specs
53 | from having or error anywhere else. *)
54 | let check_needs_base s = Re.execp or_error_re s
55 |
56 | let check_needs_todo s = Re.execp todo_re s
57 |
58 | let check_needs_not_implemented s = Re.execp not_implemented_re s
59 |
60 | let check_signatures_file fname =
61 | let sig_dat = Stdio.In_channel.read_all fname in
62 | let needs_base = check_needs_base sig_dat in
63 | let needs_todo = check_needs_todo sig_dat in
64 | let needs_not_implemented = check_needs_not_implemented sig_dat in
65 | (needs_base, needs_todo, needs_not_implemented)
66 |
67 | let print_dbl_endline s = Stdio.print_endline (s ^ "\n")
68 |
69 | let abort ?(exit_code = 1) msg =
70 | Stdio.prerr_endline ("ERROR: " ^ msg);
71 | Caml.exit exit_code
72 |
73 | let find_first re s ~sub =
74 | match Re.exec_opt re s with
75 | | None -> Or_error.error_string "regex did not match"
76 | | Some group -> (
77 | match Re.Group.get_opt group sub with
78 | | None -> Or_error.error_string "group did not match"
79 | | Some thing -> Or_error.return thing)
80 |
81 | let py_fun_name_attribute =
82 | (* TODO do we need 0-9 in there? *)
83 | Re.compile @@ Re.Perl.re "\\[@@py_fun_name\\s+([a-zA-Z_]+)\\]"
84 |
85 | let get_py_fun_name s = find_first py_fun_name_attribute s ~sub:1
86 |
87 | let py_arg_name_attribute =
88 | Re.Perl.compile_pat
89 | "\\[@@py_arg_name\\s+([a-zA-Z0-9_]+)\\s+([a-zA-Z0-9_]+)\\]"
90 |
91 | (* Given the attributes on a val_spec, pull out any mappings from ml_name to
92 | py_name. *)
93 | let get_arg_name_map attrs =
94 | match attrs with
95 | | None -> Map.empty (module String)
96 | | Some attrs ->
97 | let py_arg_names =
98 | Re.all py_arg_name_attribute attrs
99 | |> List.fold
100 | ~init:(Map.empty (module String))
101 | ~f:(fun m g ->
102 | (* For now, blow up if a key is duplicated. TODO *)
103 | Map.add_exn m ~key:(Re.Group.get g 1) ~data:(Re.Group.get g 2))
104 | in
105 | py_arg_names
106 |
107 | let find_with_default m ~key ~default = Option.value ~default @@ Map.find m key
108 |
109 | let downcase_first_letter s =
110 | let first = String.prefix s 1 in
111 | let rest = String.drop_prefix s 1 in
112 | String.lowercase first ^ rest
113 |
--------------------------------------------------------------------------------
/lib/version.ml:
--------------------------------------------------------------------------------
1 | let version = "0.4.0-SNAPSHOT"
2 |
--------------------------------------------------------------------------------
/pyml_bindgen-dev.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.4.1"
4 | synopsis: "Development package for pyml_bindgen"
5 | maintainer: ["Ryan M. Moore"]
6 | authors: ["Ryan M. Moore"]
7 | homepage: "https://github.com/mooreryan/ocaml_python_bindgen"
8 | doc: "https://mooreryan.github.io/ocaml_python_bindgen/"
9 | bug-reports: "https://github.com/mooreryan/ocaml_python_bindgen/issues"
10 | depends: [
11 | "dune" {>= "3.0"}
12 | "ocaml" {>= "4.14"}
13 | "bisect_ppx"
14 | "core" {>= "v0.15"}
15 | "core_bench" {>= "v0.15"}
16 | "core_unix" {>= "v0.15"}
17 | "ocamlformat" {>= "0.23" & < "0.24"}
18 | "ocaml-lsp-server" {>= "1.13"}
19 | "pyml"
20 | "odoc" {with-doc}
21 | ]
22 | build: [
23 | ["dune" "subst"] {dev}
24 | [
25 | "dune"
26 | "build"
27 | "-p"
28 | name
29 | "-j"
30 | jobs
31 | "@install"
32 | "@runtest" {with-test}
33 | "@doc" {with-doc}
34 | ]
35 | ]
36 | dev-repo: "git+https://github.com/mooreryan/ocaml_python_bindgen.git"
37 |
--------------------------------------------------------------------------------
/pyml_bindgen.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.4.1"
4 | synopsis: "Generate pyml bindings from OCaml value specifications"
5 | maintainer: ["Ryan M. Moore"]
6 | authors: ["Ryan M. Moore"]
7 | homepage: "https://github.com/mooreryan/ocaml_python_bindgen"
8 | doc: "https://mooreryan.github.io/ocaml_python_bindgen/"
9 | bug-reports: "https://github.com/mooreryan/ocaml_python_bindgen/issues"
10 | depends: [
11 | "dune" {>= "3.0"}
12 | "angstrom" {>= "0.15.0"}
13 | "base" {>= "v0.12"}
14 | "cmdliner" {>= "1.1.0"}
15 | "ppx_let" {>= "v0.12"}
16 | "ppx_sexp_conv" {>= "v0.12"}
17 | "ppx_string" {>= "v0.12"}
18 | "re" {>= "1.10.0"}
19 | "stdio" {>= "v0.12"}
20 | "ocaml" {>= "4.08.0"}
21 | "conf-python-3-dev" {>= "1" & with-test}
22 | "base_quickcheck" {>= "v0.12" & with-test}
23 | "ocamlformat" {>= "0.23" & < "0.24" & with-test}
24 | "ppx_assert" {>= "v0.12" & with-test}
25 | "ppx_inline_test" {>= "v0.12" & with-test}
26 | "ppx_expect" {>= "v0.12" & with-test}
27 | "pyml" {with-test}
28 | "shexp" {>= "v0.14" & with-test}
29 | "odoc" {with-doc}
30 | ]
31 | build: [
32 | ["dune" "subst"] {dev}
33 | [
34 | "dune"
35 | "build"
36 | "-p"
37 | name
38 | "-j"
39 | jobs
40 | "@install"
41 | "@runtest" {with-test}
42 | "@doc" {with-doc}
43 | ]
44 | ]
45 | dev-repo: "git+https://github.com/mooreryan/ocaml_python_bindgen.git"
46 | license: ["MIT" "Apache-2.0"]
47 |
--------------------------------------------------------------------------------
/pyml_bindgen.opam.template:
--------------------------------------------------------------------------------
1 | license: ["MIT" "Apache-2.0"]
2 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | **The cram tests in the `of_pyo_*.t` are all pretty similar, but there are slight differences between them that may trip you up if you go to update them but you're not paying close enough attention.**
2 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (names run_no_check run_option run_or_error)
3 | (libraries base pyml stdio)
4 | (preprocess (pps ppx_sexp_conv)))
5 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/run_no_check.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let silly = Silly.__init__ ~x:1 ~y:2 ()
8 |
9 | let () = print_endline ("x: " ^ Int.to_string (Silly.x silly))
10 | let () = print_endline ("y: " ^ Int.to_string (Silly.y silly))
11 | let () = print_endline ("foo: " ^ Int.to_string (Silly.foo silly ~a:10 ~b:20 ()))
12 | let () = print_endline ("bar: " ^ Int.to_string (Silly.bar ~a:10 ~b:20 ()))
13 |
14 | let () = Silly.do_nothing silly ()
15 | let () = Silly.do_nothing2 ()
16 |
17 | let () =
18 | print_endline
19 | @@ Sexp.to_string_hum ~indent:1
20 | @@ [%sexp_of: string list]
21 | @@ Silly.return_list silly ~l:[ "apple"; "pie" ] ()
22 | let () =
23 | print_endline
24 | @@ Sexp.to_string_hum ~indent:1
25 | @@ [%sexp_of: string option list]
26 | @@ Silly.return_opt_list silly ~l:[ Some "apple"; None; Some "pie" ] ()
27 |
28 | let () =
29 | print_endline
30 | @@ Sexp.to_string_hum ~indent:1
31 | @@ [%sexp_of: string array]
32 | @@ Silly.return_array silly ~a:[| "apple"; "pie" |] ()
33 | let () =
34 | print_endline
35 | @@ Sexp.to_string_hum ~indent:1
36 | @@ [%sexp_of: string option array]
37 | @@ Silly.return_opt_array silly ~a:[| Some "apple"; None; Some "pie" |] ()
38 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/run_option.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let silly = Option.value_exn (Silly.__init__ ~x:1 ~y:2 ())
8 |
9 | let () = print_endline ("x: " ^ Int.to_string (Silly.x silly))
10 | let () = print_endline ("y: " ^ Int.to_string (Silly.y silly))
11 | let () = print_endline ("foo: " ^ Int.to_string (Silly.foo silly ~a:10 ~b:20 ()))
12 | let () = print_endline ("bar: " ^ Int.to_string (Silly.bar ~a:10 ~b:20 ()))
13 |
14 | let () = Silly.do_nothing silly ()
15 | let () = Silly.do_nothing2 ()
16 |
17 | let () =
18 | print_endline
19 | @@ Sexp.to_string_hum ~indent:1
20 | @@ [%sexp_of: string list]
21 | @@ Silly.return_list silly ~l:[ "apple"; "pie" ] ()
22 | let () =
23 | print_endline
24 | @@ Sexp.to_string_hum ~indent:1
25 | @@ [%sexp_of: string option list]
26 | @@ Silly.return_opt_list silly ~l:[ Some "apple"; None; Some "pie" ] ()
27 |
28 | let () =
29 | print_endline
30 | @@ Sexp.to_string_hum ~indent:1
31 | @@ [%sexp_of: string array]
32 | @@ Silly.return_array silly ~a:[| "apple"; "pie" |] ()
33 | let () =
34 | print_endline
35 | @@ Sexp.to_string_hum ~indent:1
36 | @@ [%sexp_of: string option array]
37 | @@ Silly.return_opt_array silly ~a:[| Some "apple"; None; Some "pie" |] ()
38 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/run_or_error.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let silly = Or_error.ok_exn @@ Silly.__init__ ~x:1 ~y:2 ()
8 |
9 | let () = print_endline ("x: " ^ Int.to_string (Silly.x silly))
10 | let () = print_endline ("y: " ^ Int.to_string (Silly.y silly))
11 | let () = print_endline ("foo: " ^ Int.to_string (Silly.foo silly ~a:10 ~b:20 ()))
12 | let () = print_endline ("bar: " ^ Int.to_string (Silly.bar ~a:10 ~b:20 ()))
13 |
14 | let () = Silly.do_nothing silly ()
15 | let () = Silly.do_nothing2 ()
16 |
17 | let () =
18 | print_endline
19 | @@ Sexp.to_string_hum ~indent:1
20 | @@ [%sexp_of: string list]
21 | @@ Silly.return_list silly ~l:[ "apple"; "pie" ] ()
22 | let () =
23 | print_endline
24 | @@ Sexp.to_string_hum ~indent:1
25 | @@ [%sexp_of: string option list]
26 | @@ Silly.return_opt_list silly ~l:[ Some "apple"; None; Some "pie" ] ()
27 |
28 | let () =
29 | print_endline
30 | @@ Sexp.to_string_hum ~indent:1
31 | @@ [%sexp_of: string array]
32 | @@ Silly.return_array silly ~a:[| "apple"; "pie" |] ()
33 | let () =
34 | print_endline
35 | @@ Sexp.to_string_hum ~indent:1
36 | @@ [%sexp_of: string option array]
37 | @@ Silly.return_opt_array silly ~a:[| Some "apple"; None; Some "pie" |] ()
38 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/sigs_no_check.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:int -> y:int -> unit -> t
2 |
3 | # Attributes
4 | val x : t -> int
5 | val y : t -> int
6 |
7 | # Instance method
8 | val foo : t -> a:int -> b:int -> unit -> int
9 | val do_nothing : t -> unit -> unit
10 | val return_list : t -> l:string list -> unit -> string list
11 | val return_opt_list : t -> l:string option list -> unit -> string option list
12 | val return_array : t -> a:string array -> unit -> string array
13 | val return_opt_array : t -> a:string option array -> unit -> string option array
14 |
15 | # Class method
16 | val bar : a:int -> b:int -> unit -> int
17 | val do_nothing2 : unit -> unit
18 |
19 | # Placeholders
20 | val hello : 'a todo
21 | val world : 'a not_implemented
22 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/sigs_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:int -> y:int -> unit -> t option
2 |
3 | # Attributes
4 | val x : t -> int
5 | val y : t -> int
6 |
7 | # Instance method
8 | val foo : t -> a:int -> b:int -> unit -> int
9 | val do_nothing : t -> unit -> unit
10 | val return_list : t -> l:string list -> unit -> string list
11 | val return_opt_list : t -> l:string option list -> unit -> string option list
12 | val return_array : t -> a:string array -> unit -> string array
13 | val return_opt_array : t -> a:string option array -> unit -> string option array
14 |
15 | # Class method
16 | val bar : a:int -> b:int -> unit -> int
17 | val do_nothing2 : unit -> unit
18 |
19 | # Placeholders
20 | val hello : 'a todo
21 | val world : 'a not_implemented
22 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/sigs_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:int -> y:int -> unit -> t Or_error.t
2 |
3 | # Attributes
4 | val x : t -> int
5 | val y : t -> int
6 |
7 | # Instance method
8 | val foo : t -> a:int -> b:int -> unit -> int
9 | val do_nothing : t -> unit -> unit
10 | val return_list : t -> l:string list -> unit -> string list
11 | val return_opt_list : t -> l:string option list -> unit -> string option list
12 | val return_array : t -> a:string array -> unit -> string array
13 | val return_opt_array : t -> a:string option array -> unit -> string option array
14 |
15 | # Class method
16 | val bar : a:int -> b:int -> unit -> int
17 | val do_nothing2 : unit -> unit
18 |
19 | # Placeholders
20 | val hello : 'a todo
21 | val world : 'a not_implemented
22 |
--------------------------------------------------------------------------------
/test/basic_class_binding.t/silly.py:
--------------------------------------------------------------------------------
1 | class Silly:
2 | def __init__(self, x, y):
3 | self.x = x
4 | self.y = y
5 |
6 | def foo(self, a, b):
7 | return a + b + self.x + self.y
8 |
9 | def do_nothing(self):
10 | return None
11 |
12 | def return_list(self, l):
13 | return l
14 |
15 | def return_opt_list(self, l):
16 | return l
17 |
18 | def return_array(self, a):
19 | return a
20 |
21 | def return_opt_array(self, a):
22 | return a
23 |
24 | @staticmethod
25 | def bar(a, b):
26 | return a + b
27 |
28 | @staticmethod
29 | def do_nothing2():
30 | return None
31 |
--------------------------------------------------------------------------------
/test/binding_tuples.t/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name run)
3 | (libraries pyml))
4 |
--------------------------------------------------------------------------------
/test/binding_tuples.t/lib_ml.txt:
--------------------------------------------------------------------------------
1 | module Tuple_int_string : sig
2 | type t
3 |
4 | val make : int -> string -> t
5 |
6 | val to_pyobject : t -> Pytypes.pyobject
7 |
8 | val of_pyobject : Pytypes.pyobject -> t
9 |
10 | val print_endline : t -> unit
11 | end = struct
12 | type t = int * string
13 |
14 | let make i s = (i, s)
15 |
16 | let to_pyobject (i, s) =
17 | Py.Tuple.of_tuple2 (Py.Int.of_int i, Py.String.of_string s)
18 |
19 | let of_pyobject pyo =
20 | let i, s = Py.Tuple.to_tuple2 pyo in
21 | (Py.Int.to_int i, Py.String.to_string s)
22 |
23 | let print_endline (i, s) =
24 | print_endline @@ (string_of_int i) ^ " " ^ s
25 | end
26 |
--------------------------------------------------------------------------------
/test/binding_tuples.t/run.ml:
--------------------------------------------------------------------------------
1 | open Lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let () =
6 | Tuple_int_string.print_endline
7 | @@ Silly.foo ~x:(Tuple_int_string.make 1 "a") ()
8 |
--------------------------------------------------------------------------------
/test/binding_tuples.t/run.t:
--------------------------------------------------------------------------------
1 | These are a little brittle...they will break if ocamlformat changes
2 | the way it formats, but it is so much easier to read the generated
3 | code if it is properly formatted.
4 |
5 | Setup env
6 |
7 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
8 |
9 | Binding tuples
10 |
11 | $ if [ -f tmp ]; then rm tmp; fi
12 | $ pyml_bindgen sigs.txt silly Silly --caml-module=Silly --of-pyo-ret-type=no_check > tmp
13 |
14 | Cat the files and run.
15 |
16 | $ cat lib_ml.txt > lib.ml
17 | $ printf "\n" >> lib.ml
18 | $ cat tmp >> lib.ml
19 | $ ocamlformat --enable-outside-detected-project lib.ml
20 | module Tuple_int_string : sig
21 | type t
22 |
23 | val make : int -> string -> t
24 | val to_pyobject : t -> Pytypes.pyobject
25 | val of_pyobject : Pytypes.pyobject -> t
26 | val print_endline : t -> unit
27 | end = struct
28 | type t = int * string
29 |
30 | let make i s = (i, s)
31 |
32 | let to_pyobject (i, s) =
33 | Py.Tuple.of_tuple2 (Py.Int.of_int i, Py.String.of_string s)
34 |
35 | let of_pyobject pyo =
36 | let i, s = Py.Tuple.to_tuple2 pyo in
37 | (Py.Int.to_int i, Py.String.to_string s)
38 |
39 | let print_endline (i, s) = print_endline @@ string_of_int i ^ " " ^ s
40 | end
41 |
42 | module Silly : sig
43 | type t
44 |
45 | val of_pyobject : Pytypes.pyobject -> t
46 | val to_pyobject : t -> Pytypes.pyobject
47 | val foo : x:Tuple_int_string.t -> unit -> Tuple_int_string.t
48 | end = struct
49 | let filter_opt l = List.filter_map Fun.id l
50 | let py_module = lazy (Py.Import.import_module "silly")
51 | let import_module () = Lazy.force py_module
52 |
53 | type t = Pytypes.pyobject
54 |
55 | let of_pyobject pyo = pyo
56 | let to_pyobject x = x
57 |
58 | let foo ~x () =
59 | let class_ = Py.Module.get (import_module ()) "Silly" in
60 | let callable = Py.Object.find_attr_string class_ "foo" in
61 | let kwargs = filter_opt [ Some ("x", Tuple_int_string.to_pyobject x) ] in
62 | Tuple_int_string.of_pyobject
63 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
64 | end
65 | $ dune exec ./run.exe 2> /dev/null
66 | 10 a!!
67 |
68 |
--------------------------------------------------------------------------------
/test/binding_tuples.t/sigs.txt:
--------------------------------------------------------------------------------
1 | val foo : x:Tuple_int_string.t -> unit -> Tuple_int_string.t
2 |
--------------------------------------------------------------------------------
/test/binding_tuples.t/silly.py:
--------------------------------------------------------------------------------
1 | class Silly:
2 | @staticmethod
3 | def foo(x):
4 | return (x[0] * 10, x[1] + "!!")
5 |
--------------------------------------------------------------------------------
/test/combine_rec_modules.t/abc.ml:
--------------------------------------------------------------------------------
1 | module Apple_pie2 : sig
2 | type t
3 | end = struct
4 | type t
5 | end
6 |
7 | module Bike__thing__242__ : sig
8 | type t
9 | end = struct
10 | type t
11 | end
12 |
13 | module Cake : sig
14 | type t
15 | end = struct
16 | type t
17 | end
18 |
--------------------------------------------------------------------------------
/test/combine_rec_modules.t/d.ml:
--------------------------------------------------------------------------------
1 | module Donut : sig
2 | type t
3 | end = struct
4 | type t
5 | end
6 |
--------------------------------------------------------------------------------
/test/combine_rec_modules.t/e.ml:
--------------------------------------------------------------------------------
1 | module Extra : sig
2 | type t
3 | end = struct
4 | type t
5 | end
6 |
--------------------------------------------------------------------------------
/test/combine_rec_modules.t/no_modules.ml:
--------------------------------------------------------------------------------
1 | let x = 1
2 |
--------------------------------------------------------------------------------
/test/combine_rec_modules.t/no_sig.ml:
--------------------------------------------------------------------------------
1 | module A = struct
2 | type t
3 | end
4 |
5 | module B = struct
6 | type t
7 | end
8 |
--------------------------------------------------------------------------------
/test/combine_rec_modules.t/one_line.ml:
--------------------------------------------------------------------------------
1 | module A_a__3 : sig type t end = struct type t end
2 |
3 | module Bike : sig type t end = struct type t end
4 |
5 | module Cake_icing : sig type t end = struct type t end
6 |
--------------------------------------------------------------------------------
/test/combine_rec_modules.t/run.t:
--------------------------------------------------------------------------------
1 | If you only see one module, exit with an error. It doesn't make sense to run it
2 | if there is only one module.
3 |
4 | $ combine_rec_modules d.ml
5 | module rec Donut : sig
6 | type t
7 | end = struct
8 | type t
9 | end
10 | ERROR -- I only saw one module in the input files
11 | [1]
12 |
13 | If you don't see any modules, exit with an error.
14 |
15 | $ combine_rec_modules no_modules.ml
16 | let x = 1
17 | ERROR -- I didn't see any modules in the input files
18 | [1]
19 |
20 | Basic usage
21 |
22 | $ combine_rec_modules abc.ml
23 | module rec Apple_pie2 : sig
24 | type t
25 | end = struct
26 | type t
27 | end
28 |
29 | and Bike__thing__242__ : sig
30 | type t
31 | end = struct
32 | type t
33 | end
34 |
35 | and Cake : sig
36 | type t
37 | end = struct
38 | type t
39 | end
40 | $ combine_rec_modules d.ml e.ml
41 | module rec Donut : sig
42 | type t
43 | end = struct
44 | type t
45 | end
46 | and Extra : sig
47 | type t
48 | end = struct
49 | type t
50 | end
51 | $ combine_rec_modules abc.ml d.ml e.ml
52 | module rec Apple_pie2 : sig
53 | type t
54 | end = struct
55 | type t
56 | end
57 |
58 | and Bike__thing__242__ : sig
59 | type t
60 | end = struct
61 | type t
62 | end
63 |
64 | and Cake : sig
65 | type t
66 | end = struct
67 | type t
68 | end
69 | and Donut : sig
70 | type t
71 | end = struct
72 | type t
73 | end
74 | and Extra : sig
75 | type t
76 | end = struct
77 | type t
78 | end
79 |
80 | It works okay when there is more than just the module on the line.
81 |
82 | $ combine_rec_modules one_line.ml
83 | module rec A_a__3 : sig type t end = struct type t end
84 |
85 | and Bike : sig type t end = struct type t end
86 |
87 | and Cake_icing : sig type t end = struct type t end
88 |
89 | It does NOT check if you have a signature.
90 |
91 | $ combine_rec_modules no_sig.ml
92 | module rec A = struct
93 | type t
94 | end
95 |
96 | and B = struct
97 | type t
98 | end
99 |
100 | Stdin works by passing /dev/stdin
101 |
102 | $ cat d.ml e.ml | combine_rec_modules /dev/stdin
103 | module rec Donut : sig
104 | type t
105 | end = struct
106 | type t
107 | end
108 | and Extra : sig
109 | type t
110 | end = struct
111 | type t
112 | end
113 |
--------------------------------------------------------------------------------
/test/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name test_lib)
3 | (libraries base base_quickcheck lib shexp.process stdio)
4 | (inline_tests)
5 | (preprocess
6 | (pps
7 | ppx_assert
8 | ppx_let
9 | ppx_sexp_conv
10 | ppx_string
11 | ppx_inline_test
12 | ppx_expect)))
13 |
14 | (cram
15 | (applies_to :whole_subtree)
16 | (deps
17 | %{bin:combine_rec_modules}
18 | %{bin:gen_multi}
19 | %{bin:pyml_bindgen}
20 | ./helpers/sanitize_logs))
21 |
--------------------------------------------------------------------------------
/test/embed_source.t/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/test/embed_source.t/bad_thing.py:
--------------------------------------------------------------------------------
1 | class Thing:
2 | """A thing is pretty basic. It does have a color though!"""
3 |
4 | def __init__(self, color):
5 | """This will break :) {pyml_bindgen_string_literal| any |pyml_bindgen_string_literal}."""
6 | self.color = color
7 |
8 | def __str__(self):
9 | return(f'Thing -- color: {self.color}')
10 |
--------------------------------------------------------------------------------
/test/embed_source.t/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (names person_runner person2_runner person_thing_runner)
3 | (libraries pyml))
4 |
--------------------------------------------------------------------------------
/test/embed_source.t/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/test/embed_source.t/person.py:
--------------------------------------------------------------------------------
1 | class Person:
2 | def __init__(self, name, age):
3 | self.name = name
4 | self.age = age
5 |
6 | def __str__(self):
7 | return(f'Person -- name: {self.name}, age: {self.age}')
8 |
--------------------------------------------------------------------------------
/test/embed_source.t/person2_runner.ml:
--------------------------------------------------------------------------------
1 | let () = Py.initialize ()
2 |
3 | let hagrid = Person2.__init__ ~name:"Hagrid" ~age:111 ()
4 |
5 | let () = print_endline @@ Person2.__str__ hagrid ()
6 |
--------------------------------------------------------------------------------
/test/embed_source.t/person_runner.ml:
--------------------------------------------------------------------------------
1 | let () = Py.initialize ()
2 |
3 | let hagrid = Person.__init__ ~name:"Hagrid" ~age:111 ()
4 |
5 | let () = print_endline @@ Person.__str__ hagrid ()
6 |
--------------------------------------------------------------------------------
/test/embed_source.t/person_specs.txt:
--------------------------------------------------------------------------------
1 | val __init__ : name:string -> age:int -> unit -> t
2 | val __str__ : t -> unit -> string
3 |
--------------------------------------------------------------------------------
/test/embed_source.t/person_thing_runner.ml:
--------------------------------------------------------------------------------
1 | open Lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let hagrid = Person.__init__ ~name:"Hagrid" ~age:111 ()
6 |
7 | let thing = Thing.__init__ ~color:"orange" ()
8 |
9 | let () = print_endline @@ Person.__str__ hagrid ()
10 |
11 | let () = print_endline @@ Thing.__str__ thing ()
12 |
--------------------------------------------------------------------------------
/test/embed_source.t/py/cool_package/person2.py:
--------------------------------------------------------------------------------
1 | class Person:
2 | def __init__(self, name, age):
3 | self.name = name
4 | self.age = age
5 |
6 | def __str__(self):
7 | return(f'Person -- name: {self.name}, age: {self.age}')
8 |
--------------------------------------------------------------------------------
/test/embed_source.t/thing.py:
--------------------------------------------------------------------------------
1 | class Thing:
2 | """A thing is pretty basic. It does have a color though!"""
3 |
4 | def __init__(self, color):
5 | """Just to see if {| any |} characters mess it up."""
6 | self.color = color
7 |
8 | def __str__(self):
9 | return(f'Thing -- color: {self.color}')
10 |
--------------------------------------------------------------------------------
/test/embed_source.t/thing_specs.txt:
--------------------------------------------------------------------------------
1 | val __init__ : color:string -> unit -> t
2 | val __str__ : t -> unit -> string
3 |
--------------------------------------------------------------------------------
/test/error_messages.t/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/test/error_messages.t/run.t:
--------------------------------------------------------------------------------
1 | When missing penultimate unit arg, you get a halfway decent error
2 | message.
3 |
4 | $ pyml_bindgen val_specs.txt na na | ocamlformat --name=a.ml -
5 | ("Error generating spec for 'val bad : t -> apple:int -> int'"
6 | ("Could not create py function from val_spec."
7 | "Val specs must specify attributes, instance, class, or module methods, or placeholders."
8 | "Unless you're defining an attribute, don't forget the penultimate unit argument!"
9 | ("The bad val_spec was:"
10 | ((ml_fun_name bad)
11 | (args
12 | ((Positional ((type_ T)))
13 | (Labeled ((ml_name apple) (py_name apple) (type_ Int)))
14 | (Positional ((type_ Int)))))))))
15 |
--------------------------------------------------------------------------------
/test/error_messages.t/val_specs.txt:
--------------------------------------------------------------------------------
1 | val foo : t -> int
2 | val bad : t -> apple:int -> int
3 | val bar : a:int -> b:int -> unit -> int
4 |
--------------------------------------------------------------------------------
/test/gen_multi.t/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/test/gen_multi.t/bad.tsv:
--------------------------------------------------------------------------------
1 | apple pie
2 | is good
--------------------------------------------------------------------------------
/test/gen_multi.t/run.t:
--------------------------------------------------------------------------------
1 | Order of tsv file matters.
2 |
3 | $ gen_multi specs/cli_specs.tsv | grep '^module'
4 | module Human : sig
5 | module Cat : sig
6 |
7 | $ gen_multi specs/cli_specs_cat_first.tsv | grep '^module'
8 | module Cat : sig
9 | module Human : sig
10 |
--------------------------------------------------------------------------------
/test/gen_multi.t/specs/cat_spec.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t
2 | [@@py_fun_name __init__]
3 |
4 | val to_string : t -> unit -> string
5 | [@@py_fun_name __str__]
6 |
7 | val adopt_human : t -> human:Human.t -> unit -> unit
8 |
9 | val name : t -> string
10 | val human : t -> Human.t
--------------------------------------------------------------------------------
/test/gen_multi.t/specs/cli_specs.tsv:
--------------------------------------------------------------------------------
1 | signatures py_module py_class associated_with caml_module split_caml_module embed_python_source of_pyo_ret_type
2 | specs/human_spec.txt human Human class Human NA NA no_check
3 | specs/cat_spec.txt cat Cat class Cat NA NA no_check
4 |
--------------------------------------------------------------------------------
/test/gen_multi.t/specs/cli_specs_cat_first.tsv:
--------------------------------------------------------------------------------
1 | signatures py_module py_class associated_with caml_module split_caml_module embed_python_source of_pyo_ret_type
2 | specs/cat_spec.txt cat Cat class Cat NA NA no_check
3 | specs/human_spec.txt human Human class Human NA NA no_check
4 |
--------------------------------------------------------------------------------
/test/gen_multi.t/specs/human_spec.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t
2 | [@@py_fun_name __init__]
3 |
4 | val to_string : t -> unit -> string
5 | [@@py_fun_name __str__]
6 |
7 | val adopt_cat : t -> cat:Cat.t -> unit -> unit
8 |
9 | val name : t -> string
10 | val cat : t -> Cat.t
--------------------------------------------------------------------------------
/test/helpers/sanitize_logs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sed -E 's/#[0-9]+/PID/g;s/[0-9]{2}:[0-9]{2}:[0-9]{2}/TIME/g;s/[0-9]{4}-[0-9]{2}-[0-9]{2}/DATE/g' "${1}"
4 |
--------------------------------------------------------------------------------
/test/module_function_binding.t/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (names run_no_caml_module run_with_caml_module)
3 | (libraries base pyml stdio))
4 |
--------------------------------------------------------------------------------
/test/module_function_binding.t/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/test/module_function_binding.t/run.t:
--------------------------------------------------------------------------------
1 | These are a little brittle...they will break if ocamlformat changes
2 | the way it formats, but it is so much easier to read the generated
3 | code if it is properly formatted.
4 |
5 | Setup env
6 |
7 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
8 |
9 | Run
10 |
11 | $ if [ -f lib.ml ]; then rm lib.ml; fi
12 | $ pyml_bindgen sigs_no_check.txt silly Silly -r no_check -a module > lib.ml
13 | $ ocamlformat --enable-outside-detected-project lib.ml
14 | let filter_opt l = List.filter_map Fun.id l
15 | let py_module = lazy (Py.Import.import_module "silly")
16 | let import_module () = Lazy.force py_module
17 |
18 | type t = Pytypes.pyobject
19 |
20 | let of_pyobject pyo = pyo
21 | let to_pyobject x = x
22 |
23 | let add ~a ~b () =
24 | let callable = Py.Module.get (import_module ()) "add" in
25 | let kwargs =
26 | filter_opt [ Some ("a", Py.Int.of_int a); Some ("b", Py.Int.of_int b) ]
27 | in
28 | Py.Int.to_int @@ Py.Callable.to_function_with_keywords callable [||] kwargs
29 |
30 | let do_nothing () =
31 | let callable = Py.Module.get (import_module ()) "do_nothing" in
32 | let kwargs = filter_opt [] in
33 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
34 | $ dune exec ./run_no_caml_module.exe 2> /dev/null
35 | add: 30
36 |
37 | Run
38 |
39 | $ if [ -f lib.ml ]; then rm lib.ml; fi
40 | $ pyml_bindgen sigs_no_check.txt silly Silly -c Silly -r no_check -a module > lib.ml
41 | $ ocamlformat --enable-outside-detected-project lib.ml
42 | module Silly : sig
43 | type t
44 |
45 | val of_pyobject : Pytypes.pyobject -> t
46 | val to_pyobject : t -> Pytypes.pyobject
47 | val add : a:int -> b:int -> unit -> int
48 | val do_nothing : unit -> unit
49 | end = struct
50 | let filter_opt l = List.filter_map Fun.id l
51 | let py_module = lazy (Py.Import.import_module "silly")
52 | let import_module () = Lazy.force py_module
53 |
54 | type t = Pytypes.pyobject
55 |
56 | let of_pyobject pyo = pyo
57 | let to_pyobject x = x
58 |
59 | let add ~a ~b () =
60 | let callable = Py.Module.get (import_module ()) "add" in
61 | let kwargs =
62 | filter_opt [ Some ("a", Py.Int.of_int a); Some ("b", Py.Int.of_int b) ]
63 | in
64 | Py.Int.to_int @@ Py.Callable.to_function_with_keywords callable [||] kwargs
65 |
66 | let do_nothing () =
67 | let callable = Py.Module.get (import_module ()) "do_nothing" in
68 | let kwargs = filter_opt [] in
69 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
70 | end
71 | $ dune exec ./run_with_caml_module.exe
72 | add: 30
73 |
--------------------------------------------------------------------------------
/test/module_function_binding.t/run_no_caml_module.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let () = print_endline ("add: " ^ Int.to_string (add ~a:10 ~b:20 ()))
8 |
9 | let () = do_nothing ()
10 |
--------------------------------------------------------------------------------
/test/module_function_binding.t/run_with_caml_module.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let () = print_endline ("add: " ^ Int.to_string (Silly.add ~a:10 ~b:20 ()))
8 |
9 | let () = Silly.do_nothing ()
10 |
--------------------------------------------------------------------------------
/test/module_function_binding.t/sigs_no_check.txt:
--------------------------------------------------------------------------------
1 | val add : a:int -> b:int -> unit -> int
2 | val do_nothing : unit -> unit
3 |
--------------------------------------------------------------------------------
/test/module_function_binding.t/silly.py:
--------------------------------------------------------------------------------
1 | def add(a, b):
2 | return a + b
3 |
4 | def do_nothing():
5 | return None
6 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/creature.ml:
--------------------------------------------------------------------------------
1 | module Bug = struct
2 | module Fly : sig
3 | type t
4 |
5 | val of_pyobject : Pytypes.pyobject -> t
6 |
7 | val to_pyobject : t -> Pytypes.pyobject
8 |
9 | val make : string -> t
10 | end = struct
11 | type t = string
12 |
13 | let of_pyobject = Py.String.to_string
14 |
15 | let to_pyobject = Py.String.of_string
16 |
17 | let make s = s
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (names run_no_check run_option run_or_error)
3 | (libraries base pyml stdio)
4 | (preprocess (pps ppx_sexp_conv)))
5 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/run.t:
--------------------------------------------------------------------------------
1 | These are a little brittle...they will break if ocamlformat changes
2 | the way it formats, but it is so much easier to read the generated
3 | code if it is properly formatted.
4 |
5 | Setup env
6 |
7 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
8 |
9 | of_pyobject with no check
10 |
11 | $ if [ -f lib.ml ]; then rm lib.ml; fi
12 | $ pyml_bindgen specs_no_check.txt silly Cat --caml-module=Cat --of-pyo-ret-type=no_check > lib.ml
13 | $ ocamlformat --enable-outside-detected-project lib.ml
14 | module Cat : sig
15 | type t
16 |
17 | val of_pyobject : Pytypes.pyobject -> t
18 | val to_pyobject : t -> Pytypes.pyobject
19 | val __init__ : name:string -> unit -> t
20 | val eat : t -> fly:Creature.Bug.Fly.t -> unit -> unit
21 | val hunger : t -> int
22 | end = struct
23 | let filter_opt l = List.filter_map Fun.id l
24 | let py_module = lazy (Py.Import.import_module "silly")
25 | let import_module () = Lazy.force py_module
26 |
27 | type t = Pytypes.pyobject
28 |
29 | let of_pyobject pyo = pyo
30 | let to_pyobject x = x
31 |
32 | let __init__ ~name () =
33 | let callable = Py.Module.get (import_module ()) "Cat" in
34 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
35 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
36 |
37 | let eat t ~fly () =
38 | let callable = Py.Object.find_attr_string t "eat" in
39 | let kwargs =
40 | filter_opt [ Some ("fly", Creature.Bug.Fly.to_pyobject fly) ]
41 | in
42 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
43 |
44 | let hunger t = Py.Int.to_int @@ Py.Object.find_attr_string t "hunger"
45 | end
46 | $ dune exec ./run_no_check.exe 2> /dev/null
47 | 10
48 | 5
49 | done
50 |
51 | of_pyobject returning option
52 |
53 | $ if [ -f lib.ml ]; then rm lib.ml; fi
54 | $ pyml_bindgen specs_option.txt silly Cat --caml-module=Cat --of-pyo-ret-type=option > lib.ml
55 | $ ocamlformat --enable-outside-detected-project lib.ml
56 | module Cat : sig
57 | type t
58 |
59 | val of_pyobject : Pytypes.pyobject -> t option
60 | val to_pyobject : t -> Pytypes.pyobject
61 | val __init__ : name:string -> unit -> t option
62 | val eat : t -> fly:Creature.Bug.Fly.t -> unit -> unit
63 | val hunger : t -> int
64 | end = struct
65 | let filter_opt l = List.filter_map Fun.id l
66 | let py_module = lazy (Py.Import.import_module "silly")
67 | let import_module () = Lazy.force py_module
68 |
69 | type t = Pytypes.pyobject
70 |
71 | let is_instance pyo =
72 | let py_class = Py.Module.get (import_module ()) "Cat" in
73 | Py.Object.is_instance pyo py_class
74 |
75 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
76 | let to_pyobject x = x
77 |
78 | let __init__ ~name () =
79 | let callable = Py.Module.get (import_module ()) "Cat" in
80 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
81 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
82 |
83 | let eat t ~fly () =
84 | let callable = Py.Object.find_attr_string t "eat" in
85 | let kwargs =
86 | filter_opt [ Some ("fly", Creature.Bug.Fly.to_pyobject fly) ]
87 | in
88 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
89 |
90 | let hunger t = Py.Int.to_int @@ Py.Object.find_attr_string t "hunger"
91 | end
92 | $ dune exec ./run_option.exe 2> /dev/null
93 | 10
94 | 5
95 | done
96 |
97 | of_pyobject returning Or_error
98 |
99 | $ if [ -f lib.ml ]; then rm lib.ml; fi
100 | $ pyml_bindgen specs_or_error.txt silly Cat --caml-module=Cat --of-pyo-ret-type=or_error > lib.ml
101 | $ ocamlformat --enable-outside-detected-project lib.ml
102 | open! Base
103 |
104 | module Cat : sig
105 | type t
106 |
107 | val of_pyobject : Pytypes.pyobject -> t Or_error.t
108 | val to_pyobject : t -> Pytypes.pyobject
109 | val __init__ : name:string -> unit -> t Or_error.t
110 | val eat : t -> fly:Creature.Bug.Fly.t -> unit -> unit
111 | val hunger : t -> int
112 | end = struct
113 | let filter_opt = List.filter_opt
114 | let py_module = lazy (Py.Import.import_module "silly")
115 | let import_module () = Lazy.force py_module
116 |
117 | type t = Pytypes.pyobject
118 |
119 | let is_instance pyo =
120 | let py_class = Py.Module.get (import_module ()) "Cat" in
121 | Py.Object.is_instance pyo py_class
122 |
123 | let of_pyobject pyo =
124 | if is_instance pyo then Or_error.return pyo
125 | else Or_error.error_string "Expected Cat"
126 |
127 | let to_pyobject x = x
128 |
129 | let __init__ ~name () =
130 | let callable = Py.Module.get (import_module ()) "Cat" in
131 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
132 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
133 |
134 | let eat t ~fly () =
135 | let callable = Py.Object.find_attr_string t "eat" in
136 | let kwargs =
137 | filter_opt [ Some ("fly", Creature.Bug.Fly.to_pyobject fly) ]
138 | in
139 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
140 |
141 | let hunger t = Py.Int.to_int @@ Py.Object.find_attr_string t "hunger"
142 | end
143 | $ dune exec ./run_or_error.exe 2> /dev/null
144 | 10
145 | 5
146 | done
147 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/run_no_check.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let cat = Cat.__init__ ~name:"Sam" ()
8 |
9 | let () = print_endline @@ Int.to_string @@ Cat.hunger cat
10 |
11 | let fly = Creature.Bug.Fly.make "Bill"
12 |
13 | let () = Cat.eat cat ~fly ()
14 |
15 | let () = print_endline @@ Int.to_string @@ Cat.hunger cat
16 |
17 | let () = print_endline "done"
18 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/run_option.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let cat = Option.value_exn (Cat.__init__ ~name:"Sam" ())
8 |
9 | let () = print_endline @@ Int.to_string @@ Cat.hunger cat
10 |
11 | let fly = Creature.Bug.Fly.make "Bill"
12 |
13 | let () = Cat.eat cat ~fly ()
14 |
15 | let () = print_endline @@ Int.to_string @@ Cat.hunger cat
16 |
17 | let () = print_endline "done"
18 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/run_or_error.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let cat = Or_error.ok_exn (Cat.__init__ ~name:"Sam" ())
8 |
9 | let () = print_endline @@ Int.to_string @@ Cat.hunger cat
10 |
11 | let fly = Creature.Bug.Fly.make "Bill"
12 |
13 | let () = Cat.eat cat ~fly ()
14 |
15 | let () = print_endline @@ Int.to_string @@ Cat.hunger cat
16 |
17 | let () = print_endline "done"
18 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/silly.py:
--------------------------------------------------------------------------------
1 | class Cat:
2 | def __init__(self, name):
3 | self.name = name
4 | self.hunger = 10
5 |
6 | def eat(self, fly):
7 | # fly is pointless here, but its just an example :)
8 | self.hunger -= 5
9 |
10 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/specs_no_check.txt:
--------------------------------------------------------------------------------
1 | val __init__ : name:string -> unit -> t
2 | val eat : t -> fly:Creature.Bug.Fly.t -> unit -> unit
3 | val hunger : t -> int
4 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/specs_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : name:string -> unit -> t option
2 | val eat : t -> fly:Creature.Bug.Fly.t -> unit -> unit
3 | val hunger : t -> int
4 |
--------------------------------------------------------------------------------
/test/nested_submodules.t/specs_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : name:string -> unit -> t Or_error.t
2 | val eat : t -> fly:Creature.Bug.Fly.t -> unit -> unit
3 | val hunger : t -> int
4 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name hi)
3 | (libraries pyml))
4 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/hi.ml:
--------------------------------------------------------------------------------
1 | open Lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let () = print_endline @@ Silly.x @@ Silly.__init__ ~x:"all good!" ()
6 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/run.t:
--------------------------------------------------------------------------------
1 | Setup env
2 |
3 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
4 |
5 | Basic usage.
6 |
7 | $ if [ -f lib.ml ]; then rm lib.ml; fi
8 | $ pyml_bindgen silly_sigs_no_check.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=no_check > lib.ml
9 | $ dune exec ./hi.exe 2> /dev/null
10 | all good!
11 |
12 | Sigs with t option, but requesting no check work, but give compiler
13 | error if you try to run the code.
14 |
15 | $ if [ -f lib.ml ]; then rm lib.ml; fi
16 | $ pyml_bindgen silly_sigs_option.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=no_check > lib.ml
17 | $ dune exec ./hi.exe 2> err
18 | [1]
19 | $ grep -i -q 'signature mismatch' err
20 |
21 | Sigs with t Or_error.t, but requesting no check fail.
22 |
23 | $ if [ -f lib.ml ]; then rm lib.ml; fi
24 | $ pyml_bindgen silly_sigs_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=no_check > lib.ml 2> err
25 | [1]
26 | $ bash "${SANITIZE_LOGS}" err
27 | ERROR: You said you wanted No_check return type, but Or_error was found in the sigs.
28 |
29 | Mixed return types will give if you try to run the code. pyml_bindgen
30 | program will run fine however...
31 |
32 | $ if [ -f lib.ml ]; then rm lib.ml; fi
33 | $ pyml_bindgen silly_sigs_no_check_option.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=no_check > lib.ml
34 | $ dune exec ./hi.exe 2> err
35 | [1]
36 | $ grep -i -q 'signature mismatch' err
37 |
38 | If Or_error is present in the signatures but not passed correct
39 | --of-pyo-ret-type, it's an error.
40 |
41 | $ if [ -f lib.ml ]; then rm lib.ml; fi
42 | $ pyml_bindgen silly_sigs_no_check_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=no_check > lib.ml 2> err
43 | [1]
44 | $ bash "${SANITIZE_LOGS}" err
45 | ERROR: You said you wanted No_check return type, but Or_error was found in the sigs.
46 |
47 |
48 | $ if [ -f lib.ml ]; then rm lib.ml; fi
49 | $ pyml_bindgen silly_sigs_option_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=no_check > lib.ml 2> err
50 | [1]
51 | $ bash "${SANITIZE_LOGS}" err
52 | ERROR: You said you wanted No_check return type, but Or_error was found in the sigs.
53 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/silly_mod.py:
--------------------------------------------------------------------------------
1 | class Silly:
2 | def __init__(self, x):
3 | self.x = x
4 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/silly_sigs_no_check.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/silly_sigs_no_check_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t option
4 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/silly_sigs_no_check_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t Or_error.t
4 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/silly_sigs_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t option
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/silly_sigs_option_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t option
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t Or_error.t
4 |
--------------------------------------------------------------------------------
/test/of_pyo_no_check.t/silly_sigs_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t Or_error.t
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name hi)
3 | (libraries pyml))
4 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/hi.ml:
--------------------------------------------------------------------------------
1 | open Lib
2 |
3 | let () = Py.initialize ()
4 |
5 | let () =
6 | match Silly.__init__ ~x:"all good!" () with
7 | | Some s -> print_endline @@ Silly.x s
8 | | None -> prerr_endline "Couldn't make a Silly thing"
9 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/run.t:
--------------------------------------------------------------------------------
1 | Setup env
2 |
3 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
4 |
5 | Basic usage.
6 |
7 | $ if [ -f lib.ml ]; then rm lib.ml; fi
8 | $ pyml_bindgen silly_sigs_option.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=option > lib.ml
9 | $ dune exec ./hi.exe 2> /dev/null
10 | all good!
11 |
12 |
13 | Sigs with t no_check, but requesting option work, but give compiler
14 | error if you try to run the code.
15 |
16 | $ if [ -f lib.ml ]; then rm lib.ml; fi
17 | $ pyml_bindgen silly_sigs_no_check.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=option > lib.ml
18 | $ dune exec ./hi.exe 2> err
19 | [1]
20 | $ grep -i -q 'signature mismatch' err
21 |
22 | Sigs with t Or_error.t, but requesting option fail.
23 |
24 | $ if [ -f lib.ml ]; then rm lib.ml; fi
25 | $ pyml_bindgen silly_sigs_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=option > lib.ml 2> err
26 | [1]
27 | $ bash "${SANITIZE_LOGS}" err
28 | ERROR: You said you wanted Option return type, but Or_error was found in the sigs.
29 |
30 |
31 | Mixed return types will give if you try to run the code. pyml_bindgen
32 | program will run fine however...
33 |
34 | $ if [ -f lib.ml ]; then rm lib.ml; fi
35 | $ pyml_bindgen silly_sigs_no_check_option.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=option > lib.ml
36 | $ dune exec ./hi.exe 2> err
37 | [1]
38 | $ grep -i -q 'signature mismatch' err
39 |
40 | If Or_error is present in the signatures but not passed correct
41 | --of-pyo-ret-type, it's an error.
42 |
43 | $ if [ -f lib.ml ]; then rm lib.ml; fi
44 | $ pyml_bindgen silly_sigs_no_check_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=option > lib.ml 2> err
45 | [1]
46 | $ bash "${SANITIZE_LOGS}" err
47 | ERROR: You said you wanted Option return type, but Or_error was found in the sigs.
48 |
49 | $ if [ -f lib.ml ]; then rm lib.ml; fi
50 | $ pyml_bindgen silly_sigs_option_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=option > lib.ml 2> err
51 | [1]
52 | $ bash "${SANITIZE_LOGS}" err
53 | ERROR: You said you wanted Option return type, but Or_error was found in the sigs.
54 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/silly_mod.py:
--------------------------------------------------------------------------------
1 | class Silly:
2 | def __init__(self, x):
3 | self.x = x
4 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/silly_sigs_no_check.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/silly_sigs_no_check_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t option
4 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/silly_sigs_no_check_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t Or_error.t
4 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/silly_sigs_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t option
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/silly_sigs_option_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t option
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t Or_error.t
4 |
--------------------------------------------------------------------------------
/test/of_pyo_option.t/silly_sigs_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t Or_error.t
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name hi)
3 | (libraries base pyml))
4 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/hi.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lib
3 |
4 | let () = Py.initialize ()
5 |
6 | let () =
7 | Caml.print_endline @@ Silly.x @@ Or_error.ok_exn
8 | @@ Silly.__init__ ~x:"all good!" ()
9 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/run.t:
--------------------------------------------------------------------------------
1 | Setup env
2 |
3 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
4 |
5 | Basic usage.
6 |
7 | $ if [ -f lib.ml ]; then rm lib.ml; fi
8 | $ pyml_bindgen silly_sigs_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=or_error > lib.ml
9 | $ dune exec ./hi.exe 2> /dev/null
10 | all good!
11 |
12 | Sigs with t no_check, but requesting or_error fail.
13 |
14 | $ if [ -f lib.ml ]; then rm lib.ml; fi
15 | $ pyml_bindgen silly_sigs_no_check.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=or_error > lib.ml 2> err
16 | [1]
17 | $ bash "${SANITIZE_LOGS}" err
18 | ERROR: You said you wanted Or_error return type, but Or_error was not found in the sigs.
19 |
20 | Sigs with t option, but requesting or_error fail.
21 |
22 | $ if [ -f lib.ml ]; then rm lib.ml; fi
23 | $ pyml_bindgen silly_sigs_option.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=or_error > lib.ml 2> err
24 | [1]
25 | $ bash "${SANITIZE_LOGS}" err
26 | ERROR: You said you wanted Or_error return type, but Or_error was not found in the sigs.
27 |
28 | Mixed return types will fail.
29 |
30 | $ if [ -f lib.ml ]; then rm lib.ml; fi
31 | $ pyml_bindgen silly_sigs_no_check_option.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=or_error > lib.ml 2> err
32 | [1]
33 | $ bash "${SANITIZE_LOGS}" err
34 | ERROR: You said you wanted Or_error return type, but Or_error was not found in the sigs.
35 |
36 | If you have no_check or option mixed in with or error, and you request
37 | or_error, pyml_bindgen will NOT fail. But compiling the result, WILL
38 | fail.
39 |
40 | $ if [ -f lib.ml ]; then rm lib.ml; fi
41 | $ pyml_bindgen silly_sigs_no_check_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=or_error > lib.ml
42 | $ dune exec ./hi.exe 2> err
43 | [1]
44 | $ grep -i -q 'signature mismatch' err
45 |
46 | $ if [ -f lib.ml ]; then rm lib.ml; fi
47 | $ pyml_bindgen silly_sigs_option_or_error.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=or_error > lib.ml
48 | $ dune exec ./hi.exe 2> err
49 | [1]
50 | $ grep -i -q 'signature mismatch' err
51 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/silly_mod.py:
--------------------------------------------------------------------------------
1 | class Silly:
2 | def __init__(self, x):
3 | self.x = x
4 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/silly_sigs_no_check.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/silly_sigs_no_check_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t option
4 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/silly_sigs_no_check_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t Or_error.t
4 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/silly_sigs_option.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t option
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/silly_sigs_option_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t option
2 | val x : t -> string
3 | val bad_fun : x:string -> unit -> t Or_error.t
4 |
--------------------------------------------------------------------------------
/test/of_pyo_or_error.t/silly_sigs_or_error.txt:
--------------------------------------------------------------------------------
1 | val __init__ : x:string -> unit -> t Or_error.t
2 | val x : t -> string
3 |
--------------------------------------------------------------------------------
/test/old_bugs.t/pr_value_bug.txt:
--------------------------------------------------------------------------------
1 | # pr_value got parsed as pr_, val, ue like that by the binary, but not the lib code.
2 | val add_feature : t -> pr_name:string -> pr_value:string -> unit -> unit
3 | val add_feature2 : t -> pr_name:string -> pr_value:string -> unit -> unit
4 | val add_feature3 : t -> pr_name:string -> pr_value:string -> unit -> unit
5 |
--------------------------------------------------------------------------------
/test/old_bugs.t/run.t:
--------------------------------------------------------------------------------
1 | Setup env
2 |
3 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
4 |
5 | Basic usage.
6 |
7 | $ if [ -f lib.ml ]; then rm lib.ml; fi
8 | $ pyml_bindgen pr_value_bug.txt silly_mod Silly --caml-module=Silly --of-pyo-ret-type=no_check > lib.ml
9 | $ ocamlformat --enable --name=a.ml lib.ml
10 | module Silly : sig
11 | type t
12 |
13 | val of_pyobject : Pytypes.pyobject -> t
14 | val to_pyobject : t -> Pytypes.pyobject
15 | val add_feature : t -> pr_name:string -> pr_value:string -> unit -> unit
16 | val add_feature2 : t -> pr_name:string -> pr_value:string -> unit -> unit
17 | val add_feature3 : t -> pr_name:string -> pr_value:string -> unit -> unit
18 | end = struct
19 | let filter_opt l = List.filter_map Fun.id l
20 | let py_module = lazy (Py.Import.import_module "silly_mod")
21 | let import_module () = Lazy.force py_module
22 |
23 | type t = Pytypes.pyobject
24 |
25 | let of_pyobject pyo = pyo
26 | let to_pyobject x = x
27 |
28 | let add_feature t ~pr_name ~pr_value () =
29 | let callable = Py.Object.find_attr_string t "add_feature" in
30 | let kwargs =
31 | filter_opt
32 | [
33 | Some ("pr_name", Py.String.of_string pr_name);
34 | Some ("pr_value", Py.String.of_string pr_value);
35 | ]
36 | in
37 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
38 |
39 | let add_feature2 t ~pr_name ~pr_value () =
40 | let callable = Py.Object.find_attr_string t "add_feature2" in
41 | let kwargs =
42 | filter_opt
43 | [
44 | Some ("pr_name", Py.String.of_string pr_name);
45 | Some ("pr_value", Py.String.of_string pr_value);
46 | ]
47 | in
48 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
49 |
50 | let add_feature3 t ~pr_name ~pr_value () =
51 | let callable = Py.Object.find_attr_string t "add_feature3" in
52 | let kwargs =
53 | filter_opt
54 | [
55 | Some ("pr_name", Py.String.of_string pr_name);
56 | Some ("pr_value", Py.String.of_string pr_value);
57 | ]
58 | in
59 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
60 | end
61 |
--------------------------------------------------------------------------------
/test/py_fun_name_attr.t/dune:
--------------------------------------------------------------------------------
1 | (executables
2 | (names run)
3 | (libraries base pyml stdio))
4 |
--------------------------------------------------------------------------------
/test/py_fun_name_attr.t/run.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let cat = Cat.create ~name:"Sam" ()
8 |
9 | let () = Cat.climb cat ~how_high:20 ()
10 |
11 | let () = Cat.eat_part cat ~num_mice:0.2 ()
12 |
13 | let () = Cat.eat cat ~num_mice:2 ()
14 |
15 | let () = print_endline @@ Cat.to_string cat ()
16 |
17 | let () = print_endline "done"
18 |
--------------------------------------------------------------------------------
/test/py_fun_name_attr.t/run.t:
--------------------------------------------------------------------------------
1 | These are a little brittle...they will break if ocamlformat changes
2 | the way it formats, but it is so much easier to read the generated
3 | code if it is properly formatted.
4 |
5 | Setup env
6 |
7 | $ export SANITIZE_LOGS=$PWD/../helpers/sanitize_logs
8 |
9 | of_pyobject with no check
10 |
11 | $ if [ -f lib.ml ]; then rm lib.ml; fi
12 | $ pyml_bindgen specs.txt silly Cat --caml-module=Cat --of-pyo-ret-type=no_check > lib.ml
13 | $ ocamlformat --enable-outside-detected-project lib.ml
14 | module Cat : sig
15 | type t
16 |
17 | val of_pyobject : Pytypes.pyobject -> t
18 | val to_pyobject : t -> Pytypes.pyobject
19 | val create : name:string -> unit -> t
20 | val to_string : t -> unit -> string
21 | val eat : t -> num_mice:int -> unit -> unit
22 | val eat_part : t -> num_mice:float -> unit -> unit
23 | val climb : t -> how_high:int -> unit -> unit
24 | end = struct
25 | let filter_opt l = List.filter_map Fun.id l
26 | let py_module = lazy (Py.Import.import_module "silly")
27 | let import_module () = Lazy.force py_module
28 |
29 | type t = Pytypes.pyobject
30 |
31 | let of_pyobject pyo = pyo
32 | let to_pyobject x = x
33 |
34 | let create ~name () =
35 | let callable = Py.Module.get (import_module ()) "Cat" in
36 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
37 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
38 |
39 | let to_string t () =
40 | let callable = Py.Object.find_attr_string t "__str__" in
41 | let kwargs = filter_opt [] in
42 | Py.String.to_string
43 | @@ Py.Callable.to_function_with_keywords callable [||] kwargs
44 |
45 | let eat t ~num_mice () =
46 | let callable = Py.Object.find_attr_string t "eat" in
47 | let kwargs = filter_opt [ Some ("num_mice", Py.Int.of_int num_mice) ] in
48 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
49 |
50 | let eat_part t ~num_mice () =
51 | let callable = Py.Object.find_attr_string t "eat" in
52 | let kwargs = filter_opt [ Some ("num_mice", Py.Float.of_float num_mice) ] in
53 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
54 |
55 | let climb t ~how_high () =
56 | let callable = Py.Object.find_attr_string t "jump" in
57 | let kwargs = filter_opt [ Some ("how_high", Py.Int.of_int how_high) ] in
58 | ignore @@ Py.Callable.to_function_with_keywords callable [||] kwargs
59 | end
60 | $ dune exec ./run.exe 2> /dev/null
61 | Cat -- name: Sam, hunger: 9.0
62 | done
63 |
--------------------------------------------------------------------------------
/test/py_fun_name_attr.t/silly.py:
--------------------------------------------------------------------------------
1 | class Cat:
2 | def __init__(self, name):
3 | self.name = name
4 | self.hunger = 0
5 |
6 | def __str__(self):
7 | return(f'Cat -- name: {self.name}, hunger: {self.hunger}')
8 |
9 | def eat(self, num_mice=1):
10 | self.hunger -= (num_mice * 5)
11 | if self.hunger < 0:
12 | self.hunger = 0
13 |
14 | def jump(self, how_high=1):
15 | if how_high > 0:
16 | self.hunger += how_high
17 |
--------------------------------------------------------------------------------
/test/py_fun_name_attr.t/specs.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t
2 | [@@py_fun_name __init__]
3 | val to_string : t -> unit -> string
4 | [@@py_fun_name __str__]
5 |
6 | val eat : t -> num_mice:int -> unit -> unit
7 | val eat_part : t -> num_mice:float -> unit -> unit
8 | [@@py_fun_name eat]
9 |
10 | val climb : t -> how_high:int -> unit -> unit
11 | [@@py_fun_name jump]
12 |
--------------------------------------------------------------------------------
/test/raw_pyobjects.t/creature.ml:
--------------------------------------------------------------------------------
1 | module Bug = struct
2 | module Fly : sig
3 | type t
4 |
5 | val of_pyobject : Pytypes.pyobject -> t
6 |
7 | val to_pyobject : t -> Pytypes.pyobject
8 |
9 | val make : string -> t
10 | end = struct
11 | type t = string
12 |
13 | let of_pyobject = Py.String.to_string
14 |
15 | let to_pyobject = Py.String.of_string
16 |
17 | let make s = s
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/test/raw_pyobjects.t/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name run)
3 | (libraries base pyml stdio)
4 | (preprocess (pps ppx_sexp_conv)))
5 |
--------------------------------------------------------------------------------
/test/raw_pyobjects.t/run.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open Lib
3 | open Stdio
4 |
5 | let () = Py.initialize ()
6 |
7 | let () =
8 | print_endline @@ Int.to_string @@ Py.Int.to_int
9 | @@ Silly.concat1 ~x:(Py.Int.of_int 1) ~y:(Py.Int.of_int 2) ()
10 |
11 | let () =
12 | print_endline @@ Int.to_string @@ Py.Int.to_int
13 | @@ Silly.concat2 ~x:(Py.Int.of_int 1) ~y:(Py.Int.of_int 2) ()
14 |
15 | let () =
16 | print_endline @@ Py.String.to_string
17 | @@ Silly.concat1
18 | ~x:(Py.String.of_string "apple ")
19 | ~y:(Py.String.of_string "pie")
20 | ()
21 |
22 | let () =
23 | print_endline @@ Py.String.to_string
24 | @@ Silly.concat2
25 | ~x:(Py.String.of_string "apple ")
26 | ~y:(Py.String.of_string "pie")
27 | ()
28 |
29 | let () =
30 | print_endline @@ Int.to_string @@ Silly.concat3 ~x:1 ~y:(Py.Int.of_int 2) ()
31 |
32 | let () = print_endline "Watch out for type errors...."
33 |
34 | let () =
35 | print_s @@ [%sexp_of: string Or_error.t]
36 | @@ Or_error.try_with (fun () ->
37 | Py.String.to_string
38 | @@ Silly.concat2 ~x:(Py.Int.of_int 1)
39 | ~y:(Py.String.of_string " pie")
40 | ())
41 |
42 | let () = print_endline "done"
43 |
--------------------------------------------------------------------------------
/test/raw_pyobjects.t/silly.py:
--------------------------------------------------------------------------------
1 | def concat1(x, y):
2 | return(x + y)
3 |
4 | def concat2(x, y):
5 | return(x + y)
6 |
7 | def concat3(x, y):
8 | return(x + y)
9 |
--------------------------------------------------------------------------------
/test/raw_pyobjects.t/specs.txt:
--------------------------------------------------------------------------------
1 | val concat1 : x:Pytypes.pyobject -> y:Pytypes.pyobject -> unit -> Pytypes.pyobject
2 | val concat2 : x:Py.Object.t -> y:Py.Object.t -> unit -> Py.Object.t
3 | val concat3 : x:int -> y:Py.Object.t -> unit -> int
4 |
--------------------------------------------------------------------------------
/test/split_caml_module.t/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.23.0
3 | parse-docstrings = true
4 | wrap-comments = true
5 |
--------------------------------------------------------------------------------
/test/split_caml_module.t/dune:
--------------------------------------------------------------------------------
1 | (executable (name hello) (libraries pyml))
2 |
--------------------------------------------------------------------------------
/test/split_caml_module.t/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
--------------------------------------------------------------------------------
/test/split_caml_module.t/hello.ml:
--------------------------------------------------------------------------------
1 | let () = Py.initialize ()
2 |
3 | let () =
4 | let s =
5 | match Thing.create ~name:"Ryan" () with
6 | | None -> "oops!"
7 | | Some thing -> Thing.name thing
8 | in
9 | print_endline s
10 |
--------------------------------------------------------------------------------
/test/split_caml_module.t/run.t:
--------------------------------------------------------------------------------
1 | With trailing / at end of path.
2 |
3 | $ pyml_bindgen specs.txt thing Thing --caml-module Thing --split-caml-module a/b/c/
4 | $ ocamlformat a/b/c/thing.ml
5 | let filter_opt l = List.filter_map Fun.id l
6 | let py_module = lazy (Py.Import.import_module "thing")
7 | let import_module () = Lazy.force py_module
8 |
9 | type t = Pytypes.pyobject
10 |
11 | let is_instance pyo =
12 | let py_class = Py.Module.get (import_module ()) "Thing" in
13 | Py.Object.is_instance pyo py_class
14 |
15 | let of_pyobject pyo = if is_instance pyo then Some pyo else None
16 | let to_pyobject x = x
17 |
18 | let create ~name () =
19 | let callable = Py.Module.get (import_module ()) "Thing" in
20 | let kwargs = filter_opt [ Some ("name", Py.String.of_string name) ] in
21 | of_pyobject @@ Py.Callable.to_function_with_keywords callable [||] kwargs
22 |
23 | let name t = Py.String.to_string @@ Py.Object.find_attr_string t "name"
24 | $ ocamlformat a/b/c/thing.mli
25 | type t
26 |
27 | val of_pyobject : Pytypes.pyobject -> t option
28 | val to_pyobject : t -> Pytypes.pyobject
29 | val create : name:string -> unit -> t option
30 | val name : t -> string
31 |
32 |
33 | Without trailing / at end of path.
34 |
35 | $ pyml_bindgen specs.txt thing Thing --caml-module Thing --split-caml-module e/f/g
36 | $ diff a/b/c/thing.ml e/f/g/thing.ml
37 | $ diff a/b/c/thing.mli e/f/g/thing.mli
38 |
39 | Actually using it.
40 |
41 | $ pyml_bindgen specs.txt thing Thing --caml-module Thing --split-caml-module .
42 | $ dune exec ./hello.exe
43 | Ryan
44 |
--------------------------------------------------------------------------------
/test/split_caml_module.t/specs.txt:
--------------------------------------------------------------------------------
1 | val create : name:string -> unit -> t option
2 | [@@py_fun_name __init__]
3 |
4 | val name : t -> string
5 |
--------------------------------------------------------------------------------
/test/split_caml_module.t/thing.py:
--------------------------------------------------------------------------------
1 | class Thing:
2 | def __init__(self, name):
3 | self.name = name
4 |
--------------------------------------------------------------------------------
/test/test_specs_file.ml:
--------------------------------------------------------------------------------
1 | open! Base
2 | open! Lib
3 |
4 | let with_data_as_file ~data ~f =
5 | let open Stdio in
6 | let fname, oc = Caml.Filename.open_temp_file "pyml_bindeng_test" "" in
7 | Exn.protectx
8 | ~f:(fun oc ->
9 | Out_channel.output_string oc data;
10 | Out_channel.flush oc;
11 | f ~file_name:fname)
12 | ~finally:Out_channel.close oc
13 |
14 | let test_data =
15 | {|val add : x:int -> y:int ->
16 | unit -> int
17 |
18 | # ..........
19 | # ................
20 |
21 | # @py_fun_name=add
22 | val add_int : x:int ->
23 | y:int ->
24 | unit -> int
25 | [@@py_fun_name add]
26 |
27 |
28 | # I like cheese.
29 | #
30 | #And#Apples
31 | #Pie
32 | #
33 | # @py_fun_name=add
34 | val add_float :
35 | x:float -> y:float -> unit -> float
36 | [@@py_fun_name add]
37 |
38 |
39 | val sub : x:int -> y:int -> unit -> int
40 | [@@py_fun_name silly]
41 | [@@yummy cake_stuff]
42 | [@@thing what]
43 |
44 | # Comment at the end
45 |
46 | # HEHE
47 | |}
48 |
49 | let%expect_test _ =
50 | let specs =
51 | with_data_as_file ~data:test_data ~f:(fun ~file_name ->
52 | Specs_file.read file_name)
53 | in
54 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list] specs;
55 | [%expect
56 | {|
57 | (((attrs ()) (val_spec "val add : x:int -> y:int -> unit -> int"))
58 | ((attrs ("[@@py_fun_name add]"))
59 | (val_spec "val add_int : x:int -> y:int -> unit -> int"))
60 | ((attrs ("[@@py_fun_name add]"))
61 | (val_spec "val add_float : x:float -> y:float -> unit -> float"))
62 | ((attrs ("[@@py_fun_name silly] [@@yummy cake_stuff] [@@thing what]"))
63 | (val_spec "val sub : x:int -> y:int -> unit -> int"))) |}]
64 |
65 | let%expect_test "attributes must start a line" =
66 | let data = {| val f : int [@@apple pie] -> int |} in
67 | let specs =
68 | with_data_as_file ~data ~f:(fun ~file_name ->
69 | Or_error.try_with (fun () -> Specs_file.read file_name))
70 | in
71 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
72 | [%expect {| (Error (Failure "attributes must start a line")) |}]
73 |
74 | let%expect_test _ =
75 | let data = {|
76 | int -> int
77 | [@@apple pie]
78 | [@@is good]
79 | |} in
80 | let specs =
81 | with_data_as_file ~data ~f:(fun ~file_name ->
82 | Or_error.try_with (fun () -> Specs_file.read file_name))
83 | in
84 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
85 | [%expect
86 | {| (Error (Failure "In the middle of a val spec, but have none to work on.")) |}]
87 |
88 | let%expect_test _ =
89 | let data = {|
90 | [@@apple pie]
91 | [@@is good]
92 | |} in
93 | let specs =
94 | with_data_as_file ~data ~f:(fun ~file_name ->
95 | Or_error.try_with (fun () -> Specs_file.read file_name))
96 | in
97 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
98 | [%expect
99 | {| (Error (Failure "We have attributes but no val spec for them to go with.")) |}]
100 |
101 | let%expect_test _ =
102 | let data = {|
103 | val f : int
104 | [@@apple pie]
105 | int -> float
106 | |} in
107 | let specs =
108 | with_data_as_file ~data ~f:(fun ~file_name ->
109 | Or_error.try_with (fun () -> Specs_file.read file_name))
110 | in
111 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
112 | [%expect
113 | {| (Error (Failure "Found unused attrs but in the middle of a val spec.")) |}]
114 |
115 | let%expect_test _ =
116 | let data = {|
117 | val f : int ->
118 | float
119 | |} in
120 | let specs =
121 | with_data_as_file ~data ~f:(fun ~file_name ->
122 | Or_error.try_with (fun () -> Specs_file.read file_name))
123 | in
124 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
125 | [%expect {| (Ok (((attrs ()) (val_spec "val f : int -> float")))) |}]
126 |
127 | let%expect_test _ =
128 | let data = {|
129 | val f : int ->
130 | float
131 | [@@hello world]|} in
132 | let specs =
133 | with_data_as_file ~data ~f:(fun ~file_name ->
134 | Or_error.try_with (fun () -> Specs_file.read file_name))
135 | in
136 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
137 | [%expect
138 | {| (Ok (((attrs ("[@@hello world]")) (val_spec "val f : int -> float")))) |}]
139 |
140 | let%expect_test _ =
141 | let data = {|
142 | val f : int -> float
143 | [@@hello world]
144 | |} in
145 | let specs =
146 | with_data_as_file ~data ~f:(fun ~file_name ->
147 | Or_error.try_with (fun () -> Specs_file.read file_name))
148 | in
149 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
150 | [%expect
151 | {| (Ok (((attrs ("[@@hello world]")) (val_spec "val f : int -> float")))) |}]
152 |
153 | let%expect_test _ =
154 | let data = {| val f : int -> float |} in
155 | let specs =
156 | with_data_as_file ~data ~f:(fun ~file_name ->
157 | Or_error.try_with (fun () -> Specs_file.read file_name))
158 | in
159 | Stdio.print_s @@ [%sexp_of: Specs_file.spec list Or_error.t] specs;
160 | [%expect {| (Ok (((attrs ()) (val_spec "val f : int -> float")))) |}]
161 |
--------------------------------------------------------------------------------
/test_dev/README.md:
--------------------------------------------------------------------------------
1 | # Development tests
2 |
3 | These tests are only run in the `dev` environment.
4 |
5 | Mainly they include tests in which any part of Cmdliner's output is present. See this [Discuss post](https://discuss.ocaml.org/t/ann-cmdliner-1-1-0/9295/2) for more info.
6 |
--------------------------------------------------------------------------------
/test_dev/combine_rec_modules_cli_error.t:
--------------------------------------------------------------------------------
1 | Errors
2 |
3 | $ combine_rec_modules
4 | combine_rec_modules: required argument FILE is missing
5 | Usage: combine_rec_modules [OPTION]… FILE…
6 | Try 'combine_rec_modules --help' for more information.
7 | [1]
8 | $ combine_rec_modules missing.ml
9 | ERROR -- File missing.ml does not exist
10 | [1]
11 | $ combine_rec_modules missing.ml missing2.ml
12 | ERROR -- These files do not exist: missing.ml, missing2.ml
13 | [1]
14 |
15 |
--------------------------------------------------------------------------------
/test_dev/dune:
--------------------------------------------------------------------------------
1 | (cram
2 | (enabled_if
3 | (= %{profile} dev))
4 | (deps %{bin:combine_rec_modules} %{bin:gen_multi} %{bin:pyml_bindgen}))
5 |
--------------------------------------------------------------------------------
/test_dev/gen_multi_cli_error.t/bad.tsv:
--------------------------------------------------------------------------------
1 | apple pie
2 | is good
--------------------------------------------------------------------------------
/test_dev/gen_multi_cli_error.t/run.t:
--------------------------------------------------------------------------------
1 | Relative filenames will be relative to where the command is run, not where the
2 | file is.
3 |
4 | $ gen_multi specs/bad/bad.tsv
5 | (Sys_error "../cat_spec.txt: No such file or directory")
6 | [1]
7 |
8 | Errors
9 |
10 | $ gen_multi
11 | gen_multi: required argument CLI_OPTS is missing
12 | Usage: gen_multi [OPTION]… CLI_OPTS
13 | Try 'gen_multi --help' for more information.
14 | [1]
15 | $ gen_multi fake_file
16 | gen_multi: CLI_OPTS argument: no 'fake_file' file
17 | Usage: gen_multi [OPTION]… CLI_OPTS
18 | Try 'gen_multi --help' for more information.
19 | [1]
20 | $ gen_multi bad.tsv
21 | (Failure "bad config line: is good")
22 | [1]
23 |
24 | It tells you about all the files that are bad.
25 |
26 | $ gen_multi specs/bad_file_names.tsv
27 | ((Sys_error "specs/human_spec.txttt: No such file or directory")
28 | (Sys_error "specs/cat_spec.txttt: No such file or directory"))
29 | [1]
30 |
--------------------------------------------------------------------------------
/test_dev/gen_multi_cli_error.t/specs/bad/bad.tsv:
--------------------------------------------------------------------------------
1 | signatures py_module py_class associated_with caml_module split_caml_module embed_python_source of_pyo_ret_type
2 | ../cat_spec.txt cat Cat class Cat NA NA no_check
3 |
--------------------------------------------------------------------------------
/test_dev/gen_multi_cli_error.t/specs/bad_file_names.tsv:
--------------------------------------------------------------------------------
1 | signatures py_module py_class associated_with caml_module split_caml_module embed_python_source of_pyo_ret_type
2 | specs/human_spec.txttt human Human class Human NA NA no_check
3 | specs/cat_spec.txttt cat Cat class Cat NA NA no_check
4 |
--------------------------------------------------------------------------------
/test_dev/pyml_bindgen_cli_error.t/run.t:
--------------------------------------------------------------------------------
1 | No arguments.
2 |
3 | $ pyml_bindgen
4 | pyml_bindgen: required arguments SIGNATURES, PY_MODULE, PY_CLASS are missing
5 | Usage: pyml_bindgen [OPTION]… SIGNATURES PY_MODULE PY_CLASS
6 | Try 'pyml_bindgen --help' for more information.
7 | [1]
8 |
9 | Version screen
10 |
11 | $ pyml_bindgen --version
12 | 0.4.0-SNAPSHOT
13 |
14 | Help screen
15 |
16 | $ pyml_bindgen --help=plain
17 | NAME
18 | pyml_bindgen - generate pyml bindings for a set of signatures
19 |
20 | SYNOPSIS
21 | pyml_bindgen [OPTION]… SIGNATURES PY_MODULE PY_CLASS
22 |
23 | DESCRIPTION
24 | Generate pyml bindings from OCaml signatures.
25 |
26 | ARGUMENTS
27 | PY_CLASS (required)
28 | Python class name
29 |
30 | PY_MODULE (required)
31 | Python module name
32 |
33 | SIGNATURES (required)
34 | Path to signatures
35 |
36 | OPTIONS
37 | -a ASSOCIATED_WITH, --associated-with=ASSOCIATED_WITH (absent=class)
38 | Are the Python functions associated with a class or just a module?
39 | ASSOCIATED_WITH must be either class or module.
40 |
41 | -c CAML_MODULE, --caml-module=CAML_MODULE
42 | Write full module and signature
43 |
44 | -e PYTHON_SOURCE, --embed-python-source=PYTHON_SOURCE
45 | Use this option to embed Python source code directly in the OCaml
46 | binary. In this way, you won't have to ensure the Python
47 | interpreter can find the module at runtime.
48 |
49 | -r OF_PYO_RET_TYPE, --of-pyo-ret-type=OF_PYO_RET_TYPE (absent=option)
50 | Return type of the of_pyobject function. OF_PYO_RET_TYPE must be
51 | one of no_check, option or or_error.
52 |
53 | -s SPLIT_CAML_MODULE, --split-caml-module=SPLIT_CAML_MODULE
54 | Split sig and impl into .ml and .mli files. Puts results in the
55 | specified dir. Dir is created if it does not exist.
56 |
57 | COMMON OPTIONS
58 | --help[=FMT] (default=auto)
59 | Show this help in format FMT. The value FMT must be one of auto,
60 | pager, groff or plain. With auto, the format is pager or plain
61 | whenever the TERM env var is dumb or undefined.
62 |
63 | --version
64 | Show version information.
65 |
66 | BUGS
67 | Please report any bugs or issues on GitHub.
68 | (https://github.com/mooreryan/pyml_bindgen/issues)
69 |
70 | SEE ALSO
71 | For full documentation, please see the GitHub page.
72 | (https://github.com/mooreryan/pyml_bindgen)
73 |
74 | AUTHORS
75 | Ryan M. Moore
76 |
77 | File doesn't exist.
78 |
79 | $ pyml_bindgen apple pie good
80 | pyml_bindgen: SIGNATURES argument: no 'apple' file
81 | Usage: pyml_bindgen [OPTION]… SIGNATURES PY_MODULE PY_CLASS
82 | Try 'pyml_bindgen --help' for more information.
83 | [1]
84 |
85 | Passing `split-caml-module` without `caml-module`.
86 |
87 | $ pyml_bindgen specs.txt silly Silly --split-caml-module abc
88 | ERROR: --split-caml-module was given but --caml-module was not
89 | [1]
90 |
91 | No value for --split-caml-module
92 |
93 | $ pyml_bindgen specs.txt silly Silly --caml-module Silly --split-caml-module
94 | pyml_bindgen: option '--split-caml-module' needs an argument
95 | Usage: pyml_bindgen [OPTION]… SIGNATURES PY_MODULE PY_CLASS
96 | Try 'pyml_bindgen --help' for more information.
97 | [1]
98 |
99 |
--------------------------------------------------------------------------------
/test_dev/pyml_bindgen_cli_error.t/specs.txt:
--------------------------------------------------------------------------------
1 | val foo : t -> unit -> string
2 |
--------------------------------------------------------------------------------