From 487a49088748cf6137d5323516a1682d71684af7 Mon Sep 17 00:00:00 2001 From: cschen Date: Sat, 31 Aug 2024 15:23:38 +0200 Subject: [PATCH] fix(python): changed entry point. separated logger from driver. --- Cargo.lock | 5 +-- dist/py/codemp.pyi | 78 ++++++++++++++++++++-------------------- dist/py/pyproject.toml | 2 +- src/ffi/python/client.rs | 34 ++++++++---------- src/ffi/python/mod.rs | 62 ++++++++++++++++++-------------- 5 files changed, 92 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7148e80..d29df5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,7 +228,7 @@ dependencies = [ [[package]] name = "codemp" -version = "0.6.2" +version = "0.7.0" dependencies = [ "async-trait", "codemp-proto", @@ -254,7 +254,8 @@ dependencies = [ [[package]] name = "codemp-proto" -version = "0.6.1" +version = "0.7.0" +source = "git+ssh://git@github.com/hexedtech/codemp-proto.git?tag=v0.7.0#11cc52e837a2e568c14ef3bc3a70e8124564f5aa" dependencies = [ "prost", "tonic", diff --git a/dist/py/codemp.pyi b/dist/py/codemp.pyi index 459f923..d5c2500 100644 --- a/dist/py/codemp.pyi +++ b/dist/py/codemp.pyi @@ -8,7 +8,9 @@ class Driver: def stop(self) -> None: ... -def init(logger_cb: Callable[[str], None], debug: bool) -> Driver: ... +def init() -> Driver: ... +def set_logger(logger_cb: Callable[[str], None], debug: bool) -> bool: ... +def connect(host: str, username: str, password: str) -> Promise[Client]: ... class Promise[T]: """ @@ -22,6 +24,41 @@ class Promise[T]: def wait(self) -> T: ... def is_done(self) -> bool: ... +class Client: + """ + Handle to the actual client that manages the session. It manages the connection + to a server and joining/creating new workspaces + """ + def join_workspace(self, workspace: str) -> Promise[Workspace]: ... + def create_workspace(self, workspace: str) -> Promise[None]: ... + def delete_workspace(self, workspace: str) -> Promise[None]: ... + def invite_to_workspace(self, workspace: str, username: str) -> Promise[None]: ... + def list_workspaces(self, owned: bool, invited: bool) -> Promise[list[str]]: ... + def leave_workspace(self, workspace: str) -> bool: ... + def get_workspace(self, id: str) -> Workspace: ... + def active_workspaces(self) -> list[str]: ... + def user_id(self) -> str: ... + def user_name(self) -> str: ... + def refresh(self) -> Promise[None]: ... + +class Workspace: + """ + Handle to a workspace inside codemp. It manages buffers. + A cursor is tied to the single workspace. + """ + def create(self, path: str) -> Promise[None]: ... + def attach(self, path: str) -> Promise[BufferController]: ... + def detach(self, path: str) -> bool: ... + def fetch_buffers(self) -> Promise[None]: ... + def fetch_users(self) -> Promise[None]: ... + def list_buffer_users(self, path: str) -> Promise[list[str]]: ... + def delete(self, path: str) -> Promise[None]: ... + def id(self) -> str: ... + def cursor(self) -> CursorController: ... + def buffer_by_name(self, path: str) -> Optional[BufferController]: ... + def buffer_list(self) -> list[str]: ... + def filetree(self, filter: Optional[str]) -> list[str]: ... + class TextChange: """ Editor agnostic representation of a text change, it translate between internal @@ -85,42 +122,3 @@ class CursorController: def clear_callback(self) -> None: ... def stop(self) -> bool: ... - -class Workspace: - """ - Handle to a workspace inside codemp. It manages buffers. - A cursor is tied to the single workspace. - """ - def create(self, path: str) -> Promise[None]: ... - def attach(self, path: str) -> Promise[BufferController]: ... - def detach(self, path: str) -> bool: ... - def fetch_buffers(self) -> Promise[None]: ... - def fetch_users(self) -> Promise[None]: ... - def list_buffer_users(self, path: str) -> Promise[list[str]]: ... - def delete(self, path: str) -> Promise[None]: ... - def id(self) -> str: ... - def cursor(self) -> CursorController: ... - def buffer_by_name(self, path: str) -> Optional[BufferController]: ... - def buffer_list(self) -> list[str]: ... - def filetree(self, filter: Optional[str]) -> list[str]: ... - - -class Client: - """ - Handle to the actual client that manages the session. It manages the connection - to a server and joining/creating new workspaces - """ - def __new__(cls, - host: str, - username: str, password: str) -> Client: ... - def join_workspace(self, workspace: str) -> Promise[Workspace]: ... - def create_workspace(self, workspace: str) -> Promise[None]: ... - def delete_workspace(self, workspace: str) -> Promise[None]: ... - def invite_to_workspace(self, workspace: str, username: str) -> Promise[None]: ... - def list_workspaces(self, owned: bool, invited: bool) -> Promise[list[str]]: ... - def leave_workspace(self, workspace: str) -> bool: ... - def get_workspace(self, id: str) -> Workspace: ... - def active_workspaces(self) -> list[str]: ... - def user_id(self) -> str: ... - def user_name(self) -> str: ... - def refresh(self) -> Promise[None]: ... diff --git a/dist/py/pyproject.toml b/dist/py/pyproject.toml index f815e84..f9b6ea3 100644 --- a/dist/py/pyproject.toml +++ b/dist/py/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codemp" -version = "0.6.2" +version = "0.7.0" description = "Cooperative multi-player coding" requires-python = ">=3.8" license = {file = "../../LICENSE"} diff --git a/src/ffi/python/client.rs b/src/ffi/python/client.rs index 578f4fe..80a552c 100644 --- a/src/ffi/python/client.rs +++ b/src/ffi/python/client.rs @@ -2,25 +2,11 @@ use crate::workspace::Workspace; use crate::Client; use pyo3::prelude::*; -use super::tokio; - #[pymethods] impl Client { - #[new] - fn __new__(host: String, username: String, password: String) -> crate::Result { - tokio().block_on(Client::connect(host, username, password)) - } - - // #[pyo3(name = "join_workspace")] - // async fn pyjoin_workspace(&self, workspace: String) -> JoinHandle> { - // tracing::info!("attempting to join the workspace {}", workspace); - - // let this = self.clone(); - // async { - // tokio() - // .spawn(async move { this.join_workspace(workspace).await }) - // .await - // } + // #[new] + // fn __new__(host: String, username: String, password: String) -> crate::Result { + // tokio().block_on(Client::connect(host, username, password)) // } #[pyo3(name = "join_workspace")] @@ -52,14 +38,24 @@ impl Client { } #[pyo3(name = "invite_to_workspace")] - fn pyinvite_to_workspace(&self, py: Python<'_>, workspace: String, user: String) -> PyResult { + fn pyinvite_to_workspace( + &self, + py: Python<'_>, + workspace: String, + user: String, + ) -> PyResult { tracing::info!("attempting to invite {user} to workspace {workspace}"); let this = self.clone(); crate::a_sync_allow_threads!(py, this.invite_to_workspace(workspace, user).await) } #[pyo3(name = "list_workspaces")] - fn pylist_workspaces(&self, py: Python<'_>, owned: bool, invited: bool) -> PyResult { + fn pylist_workspaces( + &self, + py: Python<'_>, + owned: bool, + invited: bool, + ) -> PyResult { tracing::info!("attempting to list workspaces"); let this = self.clone(); crate::a_sync_allow_threads!(py, this.list_workspaces(owned, invited).await) diff --git a/src/ffi/python/mod.rs b/src/ffi/python/mod.rs index 34b6e21..334b79f 100644 --- a/src/ffi/python/mod.rs +++ b/src/ffi/python/mod.rs @@ -131,8 +131,30 @@ impl Driver { } } } + #[pyfunction] -fn init(py: Python, logging_cb: Py, debug: bool) -> PyResult { +fn init() -> PyResult { + let (rt_stop_tx, mut rt_stop_rx) = oneshot::channel::<()>(); + std::thread::spawn(move || { + tokio().block_on(async move { + tracing::info!("started runtime driver..."); + tokio::select! { + () = std::future::pending::<()>() => {}, + _ = &mut rt_stop_rx => {} + } + }) + }); + + Ok(Driver(Some(rt_stop_tx))) +} + +#[pyfunction] +fn connect(host: String, username: String, password: String) -> PyResult { + a_sync!(Client::connect(host, username, password).await) +} + +#[pyfunction] +fn set_logger(logging_cb: Py, debug: bool) -> bool { let (tx, mut rx) = mpsc::unbounded_channel(); let level = if debug { tracing::Level::DEBUG @@ -151,38 +173,22 @@ fn init(py: Python, logging_cb: Py, debug: bool) -> PyResult .with_source_location(false) .compact(); - let log_subscribing = tracing_subscriber::fmt() + let log_subscribed = tracing_subscriber::fmt() .with_ansi(false) .event_format(format) .with_max_level(level) .with_writer(std::sync::Mutex::new(LoggerProducer(tx))) - .try_init(); + .try_init() + .is_ok(); - let (rt_stop_tx, mut rt_stop_rx) = oneshot::channel::<()>(); - - match log_subscribing { - Ok(_) => { - // the runtime is driven by the logger awaiting messages from codemp and echoing them back to - // a provided logger. - py.allow_threads(move || { - std::thread::spawn(move || { - tokio().block_on(async move { - loop { - tokio::select! { - biased; - Some(msg) = rx.recv() => { - let _ = Python::with_gil(|py| logging_cb.call1(py, (msg,))); - }, - _ = &mut rt_stop_rx => { break }, // a bit brutal but will do for now. - } - } - }) - }) - }); - Ok(Driver(Some(rt_stop_tx))) - } - Err(_) => Err(PyRuntimeError::new_err("codemp was already initialised.")), + if log_subscribed { + tokio().spawn(async move { + while let Some(msg) = rx.recv().await { + let _ = Python::with_gil(|py| logging_cb.call1(py, (msg,))); + } + }); } + log_subscribed } impl From for PyErr { @@ -211,6 +217,8 @@ impl IntoPy for crate::api::User { #[pymodule] fn codemp(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(init, m)?)?; + m.add_function(wrap_pyfunction!(connect, m)?)?; + m.add_function(wrap_pyfunction!(set_logger, m)?)?; m.add_class::()?; m.add_class::()?;