python SDK (incomplete)

master
Niko PLP 2 weeks ago
parent da0f550a79
commit aff711f505
  1. 200
      Cargo.lock
  2. 1
      Cargo.toml
  3. 1
      DEV.md
  4. 2
      README.md
  5. 103
      nextgraph/examples/sparql_update.rs
  6. 1
      nextgraph/src/lib.rs
  7. 56
      nextgraph/src/local_broker.rs
  8. 7
      ng-app/src-tauri/src/lib.rs
  9. 8
      ng-net/src/app_protocol.rs
  10. 10
      ng-sdk-js/src/lib.rs
  11. 3
      ng-sdk-python/.env/pyvenv.cfg
  12. 181
      ng-sdk-python/.github/workflows/CI.yml
  13. 72
      ng-sdk-python/.gitignore
  14. 25
      ng-sdk-python/Cargo.toml
  15. 63
      ng-sdk-python/README.md
  16. 17
      ng-sdk-python/pyproject.toml
  17. 142
      ng-sdk-python/src/lib.rs
  18. 29
      ng-sdk-python/test.py
  19. 23
      ng-verifier/src/lib.rs

200
Cargo.lock generated

@ -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]]

@ -13,6 +13,7 @@ members = [
"ngone",
"ngaccount",
"ng-sdk-js",
"ng-sdk-python",
"ng-app/src-tauri",
"ng-oxigraph",
]

@ -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

@ -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

@ -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
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// 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 { <did:ng:_> <example:predicate> \"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(())
}

@ -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;
}

@ -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<AppRequestPayload>,
// ) -> Result<(Receiver<AppResponse>, 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<AppResponse>, 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<String>,
) -> 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<AppResponse, NgError> {
let mut broker = match LOCAL_BROKER.get() {

@ -527,12 +527,7 @@ async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<AppRequest, String> {
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")]

@ -684,6 +684,14 @@ impl AppRequest {
session_id: 0,
})
}
pub fn doc_fetch_repo_subscribe(repo_o: String) -> Result<Self, NgError> {
Ok(AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_from(&repo_o)?,
None,
))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]

@ -1654,12 +1654,10 @@ pub async fn doc_fetch_private_subscribe() -> Result<JsValue, String> {
#[wasm_bindgen]
pub async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<JsValue, String> {
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]

@ -0,0 +1,3 @@
home = /Applications/Xcode.app/Contents/Developer/usr/bin
include-system-site-packages = false
version = 3.7.3

@ -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-*/*

@ -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

@ -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" }

@ -0,0 +1,63 @@
<p align="center">
<img src="https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/nextgraph/.static/header.png" alt="nextgraph-header" />
</p>
# 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

@ -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"]

@ -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<PyNgError> for PyErr {
fn from(e: PyNgError) -> PyErr {
let ioe: std::io::Error = e.0.into();
ioe.into()
}
}
impl From<NgError> 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<String>,
pin: [u8; 4],
) -> PyResult<Bound<PyAny>> {
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::<PyNgError>::into(e))?;
let opened_wallet = ::nextgraph::local_broker::wallet_open_with_mnemonic_words(
&wallet,
&mnemonic_words,
pin,
)
.map_err(|e| Into::<PyNgError>::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::<PyNgError>::into(e))?;
let session = session_start(SessionConfig::new_in_memory(&user_id, &wallet_name))
.await
.map_err(|e| Into::<PyNgError>::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::<PyNgError>::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<usize>,
}
#[pyfunction]
#[pyo3(signature = (session_id, sparql, nuri=None))]
fn doc_sparql_update(
py: Python,
session_id: u64,
sparql: String,
nuri: Option<String>,
) -> PyResult<Bound<PyAny>> {
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<Bound<'a, PyAny>> {
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::<PyNgError>::into(e))?;
// stop the session
session_stop(&user_id)
.await
.map_err(|e| Into::<PyNgError>::into(e))?;
// closes the wallet
wallet_close(&wallet_name)
.await
.map_err(|e| Into::<PyNgError>::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(())
}

@ -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 { <did:ng:_> <example:predicate> \"An example value16\". }",
"did:ng:o:Dn0QpE9_4jhta1mUWRl_LZh1SbXUkXfOB5eu38PNIk4A:v:Z4ihjV3KMVIqBxzjP6hogVLyjkZunLsb7MMsCR0kizQA")
await disconnect_and_close(session_info["user"], wallet_name)
asyncio.run(main())

@ -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<u8>) -> Result<String, String> {
let triples: Vec<Triple> = serde_bare::from_slice(ser)
@ -35,6 +36,28 @@ fn triples_ser_to_json_ser(ser: &Vec<u8>) -> Result<Vec<u8>, String> {
Ok(json.as_bytes().to_vec())
}
pub fn read_triples_in_app_response_from_rust(
mut app_response: AppResponse,
) -> Result<(Vec<Triple>, Vec<Triple>), NgError> {
let mut inserts: Vec<Triple> = vec![];
let mut removes: Vec<Triple> = 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<AppResponse, String> {
if let AppResponse::V0(AppResponseV0::State(AppState { ref mut graph, .. })) = app_response {
if graph.is_some() {

Loading…
Cancel
Save