diff --git a/Cargo.lock b/Cargo.lock index 31a2f82f..4e00094a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -555,6 +555,14 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "cozo_py_module" +version = "0.1.7" +dependencies = [ + "cozo", + "pyo3", +] + [[package]] name = "cozorocks" version = "0.1.0" @@ -1275,6 +1283,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" + [[package]] name = "instant" version = "0.1.12" @@ -2177,6 +2191,66 @@ dependencies = [ "syn", ] +[[package]] +name = "pyo3" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268be0c73583c183f2b14052337465768c07726936a260f480f0857cb95ba543" +dependencies = [ + "cfg-if 1.0.0", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28fcd1e73f06ec85bf3280c48c67e731d8290ad3d730f8be9dc07946923005c8" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6cb136e222e49115b3c51c32792886defbfb0adead26a688142b346a0b9ffc" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94144a1266e236b1c932682136dc35a9dee8d3589728f68130c7c3861ef96b28" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8df9be978a2d2f0cdebabb03206ed73b11314701a5bfe71b0d753b81997777f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2840,6 +2914,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" + [[package]] name = "tempfile" version = "3.3.0" @@ -3256,6 +3336,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unindent" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 9e846b0d..1d13fe3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,4 @@ #lto = true [workspace] -members = ["cozorocks", "cozo-lib-c", "cozo-lib-java", "cozo-core", "cozoserver", "cozo-lib-wasm", "cozo-lib-swift"] +members = ["cozorocks", "cozo-lib-c", "cozo-lib-java", "cozo-core", "cozoserver", "cozo-lib-wasm", "cozo-lib-swift", "cozo-lib-python"] diff --git a/cozo-lib-python/.github_disabled/workflows/CI.yml b/cozo-lib-python/.github_disabled/workflows/CI.yml new file mode 100644 index 00000000..b34789d3 --- /dev/null +++ b/cozo-lib-python/.github_disabled/workflows/CI.yml @@ -0,0 +1,69 @@ +name: CI + +on: + push: + branches: + - main + - master + pull_request: + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: messense/maturin-action@v1 + with: + manylinux: auto + command: build + args: --release --sdist -o dist --find-interpreter + - name: Upload wheels + uses: actions/upload-artifact@v2 + with: + name: wheels + path: dist + + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - uses: messense/maturin-action@v1 + with: + command: build + args: --release -o dist --find-interpreter + - name: Upload wheels + uses: actions/upload-artifact@v2 + with: + name: wheels + path: dist + + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - uses: messense/maturin-action@v1 + with: + command: build + args: --release -o dist --universal2 --find-interpreter + - name: Upload wheels + uses: actions/upload-artifact@v2 + with: + name: wheels + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: [ macos, windows, linux ] + steps: + - uses: actions/download-artifact@v2 + with: + name: wheels + - name: Publish to PyPI + uses: messense/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --skip-existing * \ No newline at end of file diff --git a/cozo-lib-python/.gitignore b/cozo-lib-python/.gitignore new file mode 100644 index 00000000..c54e21c1 --- /dev/null +++ b/cozo-lib-python/.gitignore @@ -0,0 +1,73 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version +_test_db/ diff --git a/cozo-lib-python/Cargo.toml b/cozo-lib-python/Cargo.toml new file mode 100644 index 00000000..fba86de5 --- /dev/null +++ b/cozo-lib-python/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "cozo_py_module" +version = "0.1.7" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "cozo_embedded" +crate-type = ["cdylib"] + +[features] +#! # Features + +## Enables the `minimal`, `requests` and `graph-algo` features +compact = ["minimal", "requests", "graph-algo", "rayon"] +## Enables the `storage-sqlite` and `graph-algo` features +mobile = ["storage-sqlite", "graph-algo", "rayon"] +## Enables the `minimal`, `requests` and `graph-algo` features in single threaded mode +compact-single-threaded = ["minimal", "requests", "graph-algo"] +## Enables the `storage-sqlite` feature +minimal = ["storage-sqlite"] +## Enables the [Sqlite](https://www.sqlite.org/index.html) backend, also allows backup and restore with Sqlite data files. +storage-sqlite = ["cozo/storage-sqlite"] +## Enables the [RocksDB](http://rocksdb.org/) backend +storage-rocksdb = ["cozo/storage-rocksdb"] +## Enables the graph algorithms +graph-algo = ["cozo/graph-algo"] +## Allows the utilities to make web requests to fetch data +requests = ["cozo/requests"] +## Uses jemalloc as the global allocator, can make a difference in performance +jemalloc = ["cozo/jemalloc"] +## Enables io-uring option for the RocksDB storage +io-uring = ["cozo/io-uring"] +## Allows threading and enables the use of the `rayon` library for parallelizing algorithms +rayon = ["cozo/rayon"] +## Disallows the use of threads +nothread = ["cozo/nothread"] + + +[dependencies] +pyo3 = { version = "0.17.1", features = ["extension-module", "abi3", "abi3-py37"] } +cozo = { version = "0.1.7", path = "../cozo-core", default-features = false } diff --git a/cozo-lib-python/LICENSE.txt b/cozo-lib-python/LICENSE.txt new file mode 100644 index 00000000..82d84369 --- /dev/null +++ b/cozo-lib-python/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Ziyang Hu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/cozo-lib-python/README.md b/cozo-lib-python/README.md new file mode 100644 index 00000000..8d0b84b0 --- /dev/null +++ b/cozo-lib-python/README.md @@ -0,0 +1,14 @@ +# cozo-lib-python + +[![pypi](https://img.shields.io/pypi/v/cozo_embedded)](https://pypi.org/project/cozo_embedded/) + +Native bindings for embedding [CozoDB](https://github.com/cozodb/cozo) in Python, providing the +`cozo_embedded` package. + +You are not supposed to be using this package directly in your code. Use [PyCozo](https://github.com/cozodb/pycozo), +which depends on this package. + +To build this package, you need to install the Rust toolchain +as well as the [maturin](https://github.com/PyO3/maturin) python package. +Refer maturin's docs for how to [develop](https://www.maturin.rs/develop.html) +and [build](https://www.maturin.rs/distribution.html) this package. \ No newline at end of file diff --git a/cozo-lib-python/build.sh b/cozo-lib-python/build.sh new file mode 100755 index 00000000..e140bd1e --- /dev/null +++ b/cozo-lib-python/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +PYO3_NO_PYTHON=1 maturin build --release --strip diff --git a/cozo-lib-python/pyproject.toml b/cozo-lib-python/pyproject.toml new file mode 100644 index 00000000..430777fc --- /dev/null +++ b/cozo-lib-python/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=0.13,<0.14"] +build-backend = "maturin" + +[project] +name = "cozo_embedded" +requires-python = ">=3.7" +classifiers = [ + "Topic :: Database", + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + + diff --git a/cozo-lib-python/src/lib.rs b/cozo-lib-python/src/lib.rs new file mode 100644 index 00000000..a3e98a83 --- /dev/null +++ b/cozo-lib-python/src/lib.rs @@ -0,0 +1,70 @@ +/* + * Copyright 2022, The Cozo Project Authors. Licensed under MIT/Apache-2.0/BSD-3-Clause. + */ + +use pyo3::exceptions::PyException; +use pyo3::prelude::*; + +use cozo::*; + +#[pyclass] +struct CozoDbPy { + db: Option, +} + +const DB_CLOSED_MSG: &str = r##"{"ok":false,"message":"database closed"}"##; + +#[pymethods] +impl CozoDbPy { + #[new] + fn new(kind: &str, path: &str) -> PyResult { + match DbInstance::new(kind, path, Default::default()) { + Ok(db) => Ok(Self { db: Some(db) }), + Err(err) => Err(PyException::new_err(format!("{:?}", err))), + } + } + pub fn run_query(&self, py: Python<'_>, query: &str, params: &str) -> String { + if let Some(db) = &self.db { + py.allow_threads(|| db.run_script_str(query, params)) + } else { + DB_CLOSED_MSG.to_string() + } + } + pub fn export_relations(&self, py: Python<'_>, rels: &str) -> String { + if let Some(db) = &self.db { + py.allow_threads(|| db.export_relations_str(rels)) + } else { + DB_CLOSED_MSG.to_string() + } + } + pub fn import_relation(&self, py: Python<'_>, data: &str) -> String { + if let Some(db) = &self.db { + py.allow_threads(|| db.import_relation_str(data)) + } else { + DB_CLOSED_MSG.to_string() + } + } + pub fn backup(&self, py: Python<'_>, path: &str) -> String { + if let Some(db) = &self.db { + py.allow_threads(|| db.backup_db_str(path)) + } else { + DB_CLOSED_MSG.to_string() + } + } + pub fn restore(&self, py: Python<'_>, path: &str) -> String { + if let Some(db) = &self.db { + py.allow_threads(|| db.restore_backup_str(path)) + } else { + DB_CLOSED_MSG.to_string() + } + } + pub fn close(&mut self) -> bool { + self.db.take().is_some() + } +} + +#[pymodule] +fn cozo_embedded(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + Ok(()) +}