diff --git a/Cargo.lock b/Cargo.lock index 82aa3d2..55c3fd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,7 +352,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -397,7 +397,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -535,7 +535,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -917,10 +917,10 @@ version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -1274,7 +1274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -1339,7 +1339,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -1363,7 +1363,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -1374,7 +1374,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -1600,7 +1600,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -1795,7 +1795,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -1900,7 +1900,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -2159,7 +2159,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b" dependencies = [ "anyhow", - "heck", + "heck 0.4.1", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -2372,6 +2372,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -2589,6 +2595,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "infer" version = "0.12.0" @@ -3388,7 +3400,7 @@ dependencies = [ "serde", "sha1", "sha2 0.10.8", - "siphasher 0.3.10", + "siphasher 1.0.1", "thiserror", "zstd", ] @@ -3469,6 +3481,18 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "ng-sdk-python" +version = "0.1.1-alpha" +dependencies = [ + "async-std", + "nextgraph", + "pyo3", + "pyo3-async-runtimes", + "pythonize", + "serde", +] + [[package]] name = "ng-storage-rocksdb" version = "0.1.1-alpha" @@ -3846,7 +3870,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -4217,7 +4241,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -4338,6 +4362,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + [[package]] name = "powerfmt" version = "0.2.0" @@ -4363,7 +4393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" dependencies = [ "proc-macro2", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -4408,9 +4438,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -4430,6 +4460,92 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" +[[package]] +name = "pyo3" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset 0.9.0", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-async-runtimes" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977dc837525cfd22919ba6a831413854beb7c99a256c03bf8624ad707e45810e" +dependencies = [ + "async-std", + "futures", + "once_cell", + "pin-project-lite", + "pyo3", +] + +[[package]] +name = "pyo3-build-config" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "pythonize" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a6ee7a084f913f98d70cdc3ebec07e852b735ae3059a1500db2661265da9ff" +dependencies = [ + "pyo3", + "serde", +] + [[package]] name = "qoi" version = "0.4.1" @@ -4734,7 +4850,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.58", + "syn 2.0.98", "walkdir", ] @@ -4989,7 +5105,7 @@ checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -5011,7 +5127,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -5060,7 +5176,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -5408,9 +5524,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -5457,7 +5573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ "cfg-expr", - "heck", + "heck 0.4.1", "pkg-config", "toml", "version-compare", @@ -5525,9 +5641,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.8" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" @@ -5544,7 +5660,7 @@ dependencies = [ "glib", "glob", "gtk", - "heck", + "heck 0.4.1", "http", "jni", "libc", @@ -5586,7 +5702,7 @@ checksum = "dc47d3c84f4aeac397cd956267f3b8060c5a2dba78288a5ccf9a8b7a8c1e7025" dependencies = [ "anyhow", "cargo_toml", - "heck", + "heck 0.4.1", "json-patch", "plist", "semver", @@ -5630,7 +5746,7 @@ version = "2.0.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01cb5f945c71e040c5d191c32598565ae26cc266a9d5d4f7dd2dc324c5cfdd0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -5715,7 +5831,7 @@ dependencies = [ "ctor", "dunce", "glob", - "heck", + "heck 0.4.1", "html5ever", "infer", "json-patch", @@ -5801,7 +5917,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -5961,7 +6077,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -6072,7 +6188,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -6210,6 +6326,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "unique_id" version = "0.1.5" @@ -6451,7 +6573,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", "wasm-bindgen-shared", ] @@ -6485,7 +6607,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6619,7 +6741,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] @@ -7231,7 +7353,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.98", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 77a8feb..cfdf724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "ngone", "ngaccount", "ng-sdk-js", + "ng-sdk-python", "ng-app/src-tauri", "ng-oxigraph", ] diff --git a/DEV.md b/DEV.md index 82ee837..96d0b05 100644 --- a/DEV.md +++ b/DEV.md @@ -101,6 +101,7 @@ The crates are organized as follow : - [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services) - [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app. - [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service. +- [ng-sdk-python](ng-sdk-python/README.md) : contains the Python SDK. - ng-repo : Repositories common library - ng-net : Network common library - ng-oxigraph : Fork of OxiGraph. contains our CRDT of RDF diff --git a/README.md b/README.md index 26a8cff..2d43460 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ [![Crates.io Version](https://img.shields.io/crates/v/nextgraph)](https://crates.io/crates/nextgraph) [![docs.rs](https://img.shields.io/docsrs/nextgraph)](https://docs.rs/nextgraph) [![NPM Version](https://img.shields.io/npm/v/nextgraph)](https://www.npmjs.com/package/nextgraph) +[![PyPI - Version](https://img.shields.io/pypi/v/nextgraph)](https://pypi.org/project/nextgraph/) Rust implementation of NextGraph @@ -62,7 +63,6 @@ Licensed under either of NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively. - [rustc-image]: https://img.shields.io/badge/rustc-1.74+-blue.svg [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg [license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2 diff --git a/nextgraph/examples/sparql_update.rs b/nextgraph/examples/sparql_update.rs new file mode 100644 index 0000000..1b523d3 --- /dev/null +++ b/nextgraph/examples/sparql_update.rs @@ -0,0 +1,103 @@ +// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers +// All rights reserved. +// Licensed under the Apache License, Version 2.0 +// +// or the MIT license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::fs::read; + +use async_std::stream::StreamExt; +#[allow(unused_imports)] +use nextgraph::local_broker::{ + app_request, app_request_stream, doc_fetch_repo_subscribe, doc_sparql_update, + init_local_broker, session_start, session_stop, user_connect, user_disconnect, wallet_close, + wallet_create_v0, wallet_get, wallet_get_file, wallet_import, wallet_open_with_mnemonic_words, + wallet_read_file, wallet_was_opened, LocalBrokerConfig, SessionConfig, +}; +use nextgraph::net::types::BootstrapContentV0; +use nextgraph::repo::errors::NgError; +use nextgraph::repo::log::*; +use nextgraph::repo::types::PubKey; +use nextgraph::wallet::types::CreateWalletV0; +use nextgraph::wallet::{display_mnemonic, emojis::display_pazzle}; + +#[async_std::main] +async fn main() -> std::io::Result<()> { + // initialize the local_broker with in-memory config. + // all sessions will be lost when the program exits + init_local_broker(Box::new(|| LocalBrokerConfig::InMemory)).await; + + let wallet_file = + read("/Users/nl/Downloads/wallet-Hr-UITwGtjE1k6lXBoVGzD4FQMiDkM3T6bSeAi9PXt4A.ngw") + .expect("read wallet file"); + + let wallet = wallet_read_file(wallet_file).await?; + + let mnemonic_words = vec![ + "jealous".to_string(), + "during".to_string(), + "elevator".to_string(), + "swallow".to_string(), + "pen".to_string(), + "phone".to_string(), + "like".to_string(), + "employ".to_string(), + "myth".to_string(), + "remember".to_string(), + "question".to_string(), + "lemon".to_string(), + ]; + + let opened_wallet = wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, [2, 3, 2, 3])?; + + let user_id = opened_wallet.personal_identity(); + let wallet_name = opened_wallet.name(); + + let client = wallet_import(wallet.clone(), opened_wallet, true).await?; + + let session = session_start(SessionConfig::new_in_memory(&user_id, &wallet_name)).await?; + + // let session = session_start(SessionConfig::new_remote(&user_id, &wallet_name, None)).await?; + + // if the user has internet access, they can now decide to connect to its Server Broker, in order to sync data + let status = user_connect(&user_id).await?; + + let result = doc_sparql_update( + session.session_id, + "INSERT DATA { \"An example value10\". }".to_string(), + Some("did:ng:o:Dn0QpE9_4jhta1mUWRl_LZh1SbXUkXfOB5eu38PNIk4A:v:Z4ihjV3KMVIqBxzjP6hogVLyjkZunLsb7MMsCR0kizQA".to_string()), + ) + .await; + + log_debug!("{:?}", result); + + // // a session ID has been assigned to you in `session.session_id` you can use it to fetch a document + // let (mut receiver, cancel) = doc_fetch_repo_subscribe( + // session.session_id, + // "did:ng:o:Dn0QpE9_4jhta1mUWRl_LZh1SbXUkXfOB5eu38PNIk4A".to_string(), + // ) + // .await?; + + // cancel(); + + // while let Some(app_response) = receiver.next().await { + // let (inserts, removes) = + // nextgraph::verifier::read_triples_in_app_response_from_rust(app_response)?; + // log_debug!("inserts {:?}", inserts); + // log_debug!("removes {:?}", removes); + // } + + // Then we should disconnect + user_disconnect(&user_id).await?; + + // stop the session + session_stop(&user_id).await?; + + // closes the wallet + wallet_close(&wallet_name).await?; + + Ok(()) +} diff --git a/nextgraph/src/lib.rs b/nextgraph/src/lib.rs index e522284..ef7beff 100644 --- a/nextgraph/src/lib.rs +++ b/nextgraph/src/lib.rs @@ -94,6 +94,7 @@ pub mod verifier { pub use ng_net::app_protocol::*; } pub use ng_verifier::prepare_app_response_for_js; + pub use ng_verifier::read_triples_in_app_response_from_rust; pub use ng_verifier::triples_ser_to_json_string; } diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs index 5fbd542..82bab3b 100644 --- a/nextgraph/src/local_broker.rs +++ b/nextgraph/src/local_broker.rs @@ -2641,23 +2641,15 @@ pub async fn wallet_remove(_wallet_name: String) -> Result<(), NgError> { // should close the wallet, then remove all the saved sessions and remove the wallet } -// /// fetches a document's content. -// pub async fn doc_fetch_nuri( -// session_id: u64, -// nuri: String, -// payload: Option, -// ) -> Result<(Receiver, CancelFn), NgError> { -// let mut broker = match LOCAL_BROKER.get() { -// None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), -// Some(Ok(broker)) => broker.write().await, -// }; -// let session_id = self.get_local_session_id_for_mut(session_id)?; -// let session = broker.opened_sessions_list[session_id] -// .as_mut() -// .ok_or(NgError::SessionNotFound)?; - -// session.verifier.doc_fetch_nuri(nuri, payload, true).await -// } +/// fetches a document's content. +pub async fn doc_fetch_repo_subscribe( + session_id: u64, + repo_o: String, +) -> Result<(Receiver, CancelFn), NgError> { + let mut app_req = AppRequest::doc_fetch_repo_subscribe(repo_o)?; + app_req.set_session_id(session_id); + app_request_stream(app_req).await +} // /// fetches the private store home page and subscribes to its updates. // pub async fn doc_fetch_private( @@ -2675,6 +2667,36 @@ pub async fn wallet_remove(_wallet_name: String) -> Result<(), NgError> { // session.verifier.doc_fetch_private(true).await // } +pub async fn doc_sparql_update( + session_id: u64, + sparql: String, + nuri: Option, +) -> Result<(), String> { + let (nuri, base) = if let Some(n) = nuri { + let nuri = NuriV0::new_from(&n).map_err(|e| e.to_string())?; + let b = nuri.repo(); + (nuri, Some(b)) + } else { + (NuriV0::new_private_store_target(), None) + }; + + let request = AppRequest::V0(AppRequestV0 { + command: AppRequestCommandV0::new_write_query(), + nuri, + payload: Some(AppRequestPayload::new_sparql_query(sparql, base)), + session_id, + }); + + let res = app_request(request) + .await + .map_err(|e: NgError| e.to_string())?; + if let AppResponse::V0(AppResponseV0::Error(e)) = res { + Err(e) + } else { + Ok(()) + } +} + /// process any type of app request that returns a single value pub async fn app_request(request: AppRequest) -> Result { let mut broker = match LOCAL_BROKER.get() { diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index 2f67413..0056855 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -527,12 +527,7 @@ async fn doc_fetch_private_subscribe() -> Result { #[tauri::command(rename_all = "snake_case")] async fn doc_fetch_repo_subscribe(repo_o: String) -> Result { - let request = AppRequest::new( - AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), - NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?, - None, - ); - Ok(request) + AppRequest::doc_fetch_repo_subscribe(repo_o).map_err(|e| e.to_string()) } #[tauri::command(rename_all = "snake_case")] diff --git a/ng-net/src/app_protocol.rs b/ng-net/src/app_protocol.rs index 10d1013..4dcce79 100644 --- a/ng-net/src/app_protocol.rs +++ b/ng-net/src/app_protocol.rs @@ -684,6 +684,14 @@ impl AppRequest { session_id: 0, }) } + + pub fn doc_fetch_repo_subscribe(repo_o: String) -> Result { + Ok(AppRequest::new( + AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), + NuriV0::new_from(&repo_o)?, + None, + )) + } } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 7a1deb2..304e8df 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -1654,12 +1654,10 @@ pub async fn doc_fetch_private_subscribe() -> Result { #[wasm_bindgen] pub async fn doc_fetch_repo_subscribe(repo_o: String) -> Result { - let request = AppRequest::new( - AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), - NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?, - None, - ); - Ok(serde_wasm_bindgen::to_value(&request).unwrap()) + Ok(serde_wasm_bindgen::to_value( + &AppRequest::doc_fetch_repo_subscribe(repo_o).map_err(|e| e.to_string())?, + ) + .unwrap()) } // // #[wasm_bindgen] diff --git a/ng-sdk-python/.env/pyvenv.cfg b/ng-sdk-python/.env/pyvenv.cfg new file mode 100644 index 0000000..cf17835 --- /dev/null +++ b/ng-sdk-python/.env/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /Applications/Xcode.app/Contents/Developer/usr/bin +include-system-site-packages = false +version = 3.7.3 diff --git a/ng-sdk-python/.github/workflows/CI.yml b/ng-sdk-python/.github/workflows/CI.yml new file mode 100644 index 0000000..b8d3bc3 --- /dev/null +++ b/ng-sdk-python/.github/workflows/CI.yml @@ -0,0 +1,181 @@ +# This file is autogenerated by maturin v1.8.2 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + - runner: ubuntu-22.04 + target: s390x + - runner: ubuntu-22.04 + target: ppc64le + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + - runner: windows-latest + target: x86 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + architecture: ${{ matrix.platform.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-13 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux, musllinux, windows, macos, sdist] + permissions: + # Use to sign the release artifacts + id-token: write + # Used to upload release artifacts + contents: write + # Used to generate artifact attestation + attestations: write + steps: + - uses: actions/download-artifact@v4 + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-path: 'wheels-*/*' + - name: Publish to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/ng-sdk-python/.gitignore b/ng-sdk-python/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/ng-sdk-python/.gitignore @@ -0,0 +1,72 @@ +/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 diff --git a/ng-sdk-python/Cargo.toml b/ng-sdk-python/Cargo.toml new file mode 100644 index 0000000..2d98ce4 --- /dev/null +++ b/ng-sdk-python/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ng-sdk-python" +version.workspace = true +description = "NextGraph python package. Nextgraph is a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs" +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +keywords = [ "crdt","e2ee","local-first","p2p","semantic-web" ] +documentation.workspace = true +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "nextgraph" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.23.3" +pyo3-async-runtimes = { version = "0.23", features = ["async-std-runtime"] } +pythonize = "0.23.0" +async-std = "1.12.0" +serde = { version = "1.0.142", features = ["derive"] } +nextgraph = { path = "../nextgraph" } diff --git a/ng-sdk-python/README.md b/ng-sdk-python/README.md new file mode 100644 index 0000000..557aff3 --- /dev/null +++ b/ng-sdk-python/README.md @@ -0,0 +1,63 @@ +

+ nextgraph-header +

+ +# nextgraph + +![MSRV][rustc-image] +[![Apache 2.0 Licensed][license-image]][license-link] +[![MIT Licensed][license-image2]][license-link2] +[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://forum.nextgraph.org) +[![PyPI - Version](https://img.shields.io/pypi/v/nextgraph)](https://pypi.org/project/nextgraph/) + +Python package for NextGraph, implemented in Rust + +This repository is in active development at [https://git.nextgraph.org/NextGraph/nextgraph-rs](https://git.nextgraph.org/NextGraph/nextgraph-rs), a Gitea instance. For bug reports, issues, merge requests, and in order to join the dev team, please visit the link above and create an account (you can do so with a github account). The [github repo](https://github.com/nextgraph-org/nextgraph-rs) is just a read-only mirror that does not accept issues. + +## NextGraph + +> NextGraph brings about the convergence of P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs. +> +> This open source ecosystem provides solutions for end-users (a platform) and software developers (a framework), wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers. +> +> More info here [https://nextgraph.org](https://nextgraph.org) + +## Support + +Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org) + +And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org) + +[![Mastodon](https://img.shields.io/badge/-MASTODON-%232B90D9?style=for-the-badge&logo=mastodon&logoColor=white)](https://fosstodon.org/@nextgraph) + +## How to use NextGraph App & Platform + +NextGraph is in alpha release! + +You can try it online or by installing the apps. Please follow our [Getting started](https://docs.nextgraph.org/en/getting-started/) guide . + +You can also subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/). + +## NextGraph is also a Framework for App developers + +Read our [getting started guide for developers](https://docs.nextgraph.org/en/framework/getting-started/). + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + at your option. + +`SPDX-License-Identifier: Apache-2.0 OR MIT` + +--- + +NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively. + +[rustc-image]: https://img.shields.io/badge/rustc-1.74+-blue.svg +[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg +[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2 +[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg +[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT diff --git a/ng-sdk-python/pyproject.toml b/ng-sdk-python/pyproject.toml new file mode 100644 index 0000000..f846b3c --- /dev/null +++ b/ng-sdk-python/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +requires = ["maturin>=1.8,<2.0"] +build-backend = "maturin" + +[project] +name = "nextgraph" +requires-python = ">=3.7.3" +description = "NextGraph brings about the convergence of P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs." +readme = "README.md" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +version = "0.1a1.dev2" +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/ng-sdk-python/src/lib.rs b/ng-sdk-python/src/lib.rs new file mode 100644 index 0000000..e257d5b --- /dev/null +++ b/ng-sdk-python/src/lib.rs @@ -0,0 +1,142 @@ +use pyo3::exceptions::PyTypeError; +use pyo3::prelude::*; +use pythonize::{depythonize, pythonize}; +use serde::{Deserialize, Serialize}; + +use std::fs::read; + +#[allow(unused_imports)] +use ::nextgraph::local_broker::{ + app_request, app_request_stream, doc_fetch_repo_subscribe, init_local_broker, session_start, + session_stop, user_connect, user_disconnect, wallet_close, wallet_create_v0, wallet_get, + wallet_get_file, wallet_import, wallet_read_file, wallet_was_opened, LocalBrokerConfig, + SessionConfig, +}; +use ::nextgraph::net::types::BootstrapContentV0; +use ::nextgraph::repo::errors::NgError; +use ::nextgraph::repo::log::*; +use ::nextgraph::repo::types::PubKey; +use ::nextgraph::wallet::types::{CreateWalletV0, SessionInfo}; +use ::nextgraph::wallet::{display_mnemonic, emojis::display_pazzle}; +use async_std::stream::StreamExt; + +#[pyfunction] +fn init_local_broker_in_memory() -> PyResult<()> { + Ok(()) +} + +struct PyNgError(NgError); + +impl From for PyErr { + fn from(e: PyNgError) -> PyErr { + let ioe: std::io::Error = e.0.into(); + ioe.into() + } +} + +impl From for PyNgError { + fn from(e: NgError) -> PyNgError { + PyNgError(e) + } +} + +/// Open the wallet with mnemonic and PIN, and returns the wallet_name and the SessionInfo +#[pyfunction] +fn wallet_open_with_mnemonic_words( + py: Python, + wallet_file_path: String, + mnemonic_words: Vec, + pin: [u8; 4], +) -> PyResult> { + pyo3_async_runtimes::async_std::future_into_py(py, async move { + init_local_broker(Box::new(|| LocalBrokerConfig::InMemory)).await; + + let wallet_file = read(wallet_file_path).expect("read wallet file"); + + let wallet = wallet_read_file(wallet_file) + .await + .map_err(|e| Into::::into(e))?; + + let opened_wallet = ::nextgraph::local_broker::wallet_open_with_mnemonic_words( + &wallet, + &mnemonic_words, + pin, + ) + .map_err(|e| Into::::into(e))?; + + let user_id = opened_wallet.personal_identity(); + let wallet_name = opened_wallet.name(); + + let _client = wallet_import(wallet.clone(), opened_wallet, true) + .await + .map_err(|e| Into::::into(e))?; + + let session = session_start(SessionConfig::new_in_memory(&user_id, &wallet_name)) + .await + .map_err(|e| Into::::into(e))?; + + // let session = session_start(SessionConfig::new_remote(&user_id, &wallet_name, None)).await?; + + let _status = user_connect(&user_id) + .await + .map_err(|e| Into::::into(e))?; + + let s = Python::with_gil(|py| pythonize(py, &session).unwrap().unbind()); + Ok((wallet_name, s)) + }) +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct Sample { + foo: u64, + bar: Option, +} + +#[pyfunction] +#[pyo3(signature = (session_id, sparql, nuri=None))] +fn doc_sparql_update( + py: Python, + session_id: u64, + sparql: String, + nuri: Option, +) -> PyResult> { + pyo3_async_runtimes::async_std::future_into_py(py, async move { + ::nextgraph::local_broker::doc_sparql_update(session_id, sparql, nuri) + .await + .map_err(|e| PyTypeError::new_err(e))?; + Ok(()) + }) +} + +#[pyfunction] +fn disconnect_and_close<'a>( + py: Python<'a>, + user_id: Bound<'a, PyAny>, + wallet_name: String, +) -> PyResult> { + let user_id: PubKey = depythonize(&user_id)?; + pyo3_async_runtimes::async_std::future_into_py(py, async move { + user_disconnect(&user_id) + .await + .map_err(|e| Into::::into(e))?; + + // stop the session + session_stop(&user_id) + .await + .map_err(|e| Into::::into(e))?; + + // closes the wallet + wallet_close(&wallet_name) + .await + .map_err(|e| Into::::into(e))?; + Ok(()) + }) +} + +#[pymodule] +fn nextgraph(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(wallet_open_with_mnemonic_words, m)?)?; + m.add_function(wrap_pyfunction!(doc_sparql_update, m)?)?; + m.add_function(wrap_pyfunction!(disconnect_and_close, m)?)?; + Ok(()) +} diff --git a/ng-sdk-python/test.py b/ng-sdk-python/test.py new file mode 100644 index 0000000..3ca7d03 --- /dev/null +++ b/ng-sdk-python/test.py @@ -0,0 +1,29 @@ +import asyncio +from nextgraph import wallet_open_with_mnemonic_words, doc_sparql_update, disconnect_and_close + +async def main(): + wallet_session = await wallet_open_with_mnemonic_words( + "/Users/nl/Downloads/wallet-Hr-UITwGtjE1k6lXBoVGzD4FQMiDkM3T6bSeAi9PXt4A.ngw", + ["jealous", + "during", + "elevator", + "swallow", + "pen", + "phone", + "like", + "employ", + "myth", + "remember", + "question", + "lemon"], + [2, 3, 2, 3]) + wallet_name = wallet_session[0] + session_info = wallet_session[1] + print(wallet_name) + print(session_info) + await doc_sparql_update(session_info["session_id"], + "INSERT DATA { \"An example value16\". }", + "did:ng:o:Dn0QpE9_4jhta1mUWRl_LZh1SbXUkXfOB5eu38PNIk4A:v:Z4ihjV3KMVIqBxzjP6hogVLyjkZunLsb7MMsCR0kizQA") + await disconnect_and_close(session_info["user"], wallet_name) + +asyncio.run(main()) \ No newline at end of file diff --git a/ng-verifier/src/lib.rs b/ng-verifier/src/lib.rs index 1dbe491..f6cbb0c 100644 --- a/ng-verifier/src/lib.rs +++ b/ng-verifier/src/lib.rs @@ -16,6 +16,7 @@ mod rocksdb_user_storage; use ng_net::app_protocol::*; use ng_oxigraph::oxrdf::Triple; +use ng_repo::errors::NgError; pub fn triples_ser_to_json_string(ser: &Vec) -> Result { let triples: Vec = serde_bare::from_slice(ser) @@ -35,6 +36,28 @@ fn triples_ser_to_json_ser(ser: &Vec) -> Result, String> { Ok(json.as_bytes().to_vec()) } +pub fn read_triples_in_app_response_from_rust( + mut app_response: AppResponse, +) -> Result<(Vec, Vec), NgError> { + let mut inserts: Vec = vec![]; + let mut removes: Vec = vec![]; + if let AppResponse::V0(AppResponseV0::State(AppState { ref mut graph, .. })) = app_response { + if graph.is_some() { + let graph_state = graph.take().unwrap(); + inserts = serde_bare::from_slice(&graph_state.triples)?; + }; + } else if let AppResponse::V0(AppResponseV0::Patch(AppPatch { ref mut graph, .. })) = + app_response + { + if graph.is_some() { + let graph_patch = graph.take().unwrap(); + inserts = serde_bare::from_slice(&graph_patch.inserts)?; + removes = serde_bare::from_slice(&graph_patch.removes)?; + }; + } + Ok((inserts, removes)) +} + pub fn prepare_app_response_for_js(mut app_response: AppResponse) -> Result { if let AppResponse::V0(AppResponseV0::State(AppState { ref mut graph, .. })) = app_response { if graph.is_some() {