fix(python): changed entry point. separated logger from driver.

This commit is contained in:
cschen 2024-08-31 15:23:38 +02:00
parent 7fc25cd332
commit 487a490887
5 changed files with 92 additions and 89 deletions

5
Cargo.lock generated
View file

@ -228,7 +228,7 @@ dependencies = [
[[package]] [[package]]
name = "codemp" name = "codemp"
version = "0.6.2" version = "0.7.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"codemp-proto", "codemp-proto",
@ -254,7 +254,8 @@ dependencies = [
[[package]] [[package]]
name = "codemp-proto" 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 = [ dependencies = [
"prost", "prost",
"tonic", "tonic",

78
dist/py/codemp.pyi vendored
View file

@ -8,7 +8,9 @@ class Driver:
def stop(self) -> None: ... 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]: class Promise[T]:
""" """
@ -22,6 +24,41 @@ class Promise[T]:
def wait(self) -> T: ... def wait(self) -> T: ...
def is_done(self) -> bool: ... 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: class TextChange:
""" """
Editor agnostic representation of a text change, it translate between internal Editor agnostic representation of a text change, it translate between internal
@ -85,42 +122,3 @@ class CursorController:
def clear_callback(self) -> None: ... def clear_callback(self) -> None: ...
def stop(self) -> bool: ... 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]: ...

View file

@ -1,6 +1,6 @@
[project] [project]
name = "codemp" name = "codemp"
version = "0.6.2" version = "0.7.0"
description = "Cooperative multi-player coding" description = "Cooperative multi-player coding"
requires-python = ">=3.8" requires-python = ">=3.8"
license = {file = "../../LICENSE"} license = {file = "../../LICENSE"}

View file

@ -2,25 +2,11 @@ use crate::workspace::Workspace;
use crate::Client; use crate::Client;
use pyo3::prelude::*; use pyo3::prelude::*;
use super::tokio;
#[pymethods] #[pymethods]
impl Client { impl Client {
#[new] // #[new]
fn __new__(host: String, username: String, password: String) -> crate::Result<Self> { // fn __new__(host: String, username: String, password: String) -> crate::Result<Self> {
tokio().block_on(Client::connect(host, username, password)) // tokio().block_on(Client::connect(host, username, password))
}
// #[pyo3(name = "join_workspace")]
// async fn pyjoin_workspace(&self, workspace: String) -> JoinHandle<crate::Result<Workspace>> {
// tracing::info!("attempting to join the workspace {}", workspace);
// let this = self.clone();
// async {
// tokio()
// .spawn(async move { this.join_workspace(workspace).await })
// .await
// }
// } // }
#[pyo3(name = "join_workspace")] #[pyo3(name = "join_workspace")]
@ -52,14 +38,24 @@ impl Client {
} }
#[pyo3(name = "invite_to_workspace")] #[pyo3(name = "invite_to_workspace")]
fn pyinvite_to_workspace(&self, py: Python<'_>, workspace: String, user: String) -> PyResult<super::Promise> { fn pyinvite_to_workspace(
&self,
py: Python<'_>,
workspace: String,
user: String,
) -> PyResult<super::Promise> {
tracing::info!("attempting to invite {user} to workspace {workspace}"); tracing::info!("attempting to invite {user} to workspace {workspace}");
let this = self.clone(); let this = self.clone();
crate::a_sync_allow_threads!(py, this.invite_to_workspace(workspace, user).await) crate::a_sync_allow_threads!(py, this.invite_to_workspace(workspace, user).await)
} }
#[pyo3(name = "list_workspaces")] #[pyo3(name = "list_workspaces")]
fn pylist_workspaces(&self, py: Python<'_>, owned: bool, invited: bool) -> PyResult<super::Promise> { fn pylist_workspaces(
&self,
py: Python<'_>,
owned: bool,
invited: bool,
) -> PyResult<super::Promise> {
tracing::info!("attempting to list workspaces"); tracing::info!("attempting to list workspaces");
let this = self.clone(); let this = self.clone();
crate::a_sync_allow_threads!(py, this.list_workspaces(owned, invited).await) crate::a_sync_allow_threads!(py, this.list_workspaces(owned, invited).await)

View file

@ -131,8 +131,30 @@ impl Driver {
} }
} }
} }
#[pyfunction] #[pyfunction]
fn init(py: Python, logging_cb: Py<PyFunction>, debug: bool) -> PyResult<Driver> { fn init() -> PyResult<Driver> {
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<Promise> {
a_sync!(Client::connect(host, username, password).await)
}
#[pyfunction]
fn set_logger(logging_cb: Py<PyFunction>, debug: bool) -> bool {
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();
let level = if debug { let level = if debug {
tracing::Level::DEBUG tracing::Level::DEBUG
@ -151,38 +173,22 @@ fn init(py: Python, logging_cb: Py<PyFunction>, debug: bool) -> PyResult<Driver>
.with_source_location(false) .with_source_location(false)
.compact(); .compact();
let log_subscribing = tracing_subscriber::fmt() let log_subscribed = tracing_subscriber::fmt()
.with_ansi(false) .with_ansi(false)
.event_format(format) .event_format(format)
.with_max_level(level) .with_max_level(level)
.with_writer(std::sync::Mutex::new(LoggerProducer(tx))) .with_writer(std::sync::Mutex::new(LoggerProducer(tx)))
.try_init(); .try_init()
.is_ok();
let (rt_stop_tx, mut rt_stop_rx) = oneshot::channel::<()>(); if log_subscribed {
tokio().spawn(async move {
match log_subscribing { while let Some(msg) = rx.recv().await {
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,))); 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.")),
} }
log_subscribed
} }
impl From<crate::Error> for PyErr { impl From<crate::Error> for PyErr {
@ -211,6 +217,8 @@ impl IntoPy<PyObject> for crate::api::User {
#[pymodule] #[pymodule]
fn codemp(m: &Bound<'_, PyModule>) -> PyResult<()> { fn codemp(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(init, m)?)?; 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::<Driver>()?; m.add_class::<Driver>()?;
m.add_class::<TextChange>()?; m.add_class::<TextChange>()?;