feat(python): better struct access

Co-authored-by: alemi <me@alemi.dev>
This commit is contained in:
zaaarf 2024-10-16 02:46:20 +02:00
parent e1e09cb20e
commit 3326217058
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
7 changed files with 144 additions and 29 deletions

7
Cargo.lock generated
View file

@ -703,6 +703,12 @@ version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "inventory"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
[[package]]
name = "itertools"
version = "0.13.0"
@ -1269,6 +1275,7 @@ checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51"
dependencies = [
"cfg-if",
"indoc",
"inventory",
"libc",
"memoffset",
"once_cell",

View file

@ -51,7 +51,7 @@ napi = { version = "2.16", features = ["full"], optional = true }
napi-derive = { version="2.16", optional = true}
# glue (python)
pyo3 = { version = "0.22", features = ["extension-module"], optional = true}
pyo3 = { version = "0.22", features = ["extension-module", "multiple-pymethods"], optional = true}
# extra
async-trait = { version = "0.1", optional = true }

View file

@ -13,7 +13,6 @@ import java.util.OptionalLong;
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class TextChange {
/**
* The starting position of the change.

View file

@ -24,7 +24,7 @@ class Config:
port: Optional[int]
tls: Optional[bool]
def __new__(cls, *, username: str, password: str, **kwargs) -> Config: ...
def __new__(cls, username: str, password: str, **kwargs) -> Config: ...
def init() -> Driver: ...
def set_logger(logger_cb: Callable[[str], None], debug: bool) -> bool: ...

View file

@ -6,11 +6,13 @@ use uuid::Uuid;
/// Represents a service user
#[derive(Debug, Clone)]
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct User {
/// User unique identifier, should never change.
pub id: Uuid,
/// User name, can change but should be unique.
#[pyo3(get, set)]
pub name: String,
}

View file

@ -80,10 +80,9 @@
//! import codemp
//!
//! # connect first, api.code.mp is managed by hexed.technology
//! config = codemp.get_default_config()
//! config.username = "mail@example.net"
//! config.password = "dont-use-this-password"
//! client = codemp.connect(config).wait()
//! client = codemp.connect(
//! codemp.Config('mail@example.net', 'dont-use-this-password')
//! ).wait()
//!
//! # create and join a workspace
//! client.create_workspace("some-workspace").wait()
@ -94,14 +93,16 @@
//! buffer = workspace.attach_buffer("/my/file.txt").wait()
//!
//! # write `hello!` at the beginning of this buffer
//! buffer.send(
//! 0, 0, "hello!"
//! ).wait()
//! buffer.send(codemp.TextChange(
//! start=0, end=0,
//! content="hello!"
//! )).wait()
//!
//! # wait for cursor movements
//! while true:
//! event = workspace.cursor().recv().wait()
//! print(f"user {event.user} moved on buffer {event.buffer}")
//!
//! ```
//!
//! ## Lua

View file

@ -3,14 +3,14 @@ pub mod controllers;
pub mod workspace;
use crate::{
api::{Config, Cursor, TextChange},
api::{BufferUpdate, Config, Cursor, Selection, TextChange, User},
buffer::Controller as BufferController,
cursor::Controller as CursorController,
Client, Workspace,
};
use pyo3::prelude::*;
use pyo3::{
prelude::*,
exceptions::{PyConnectionError, PyRuntimeError, PySystemError},
types::PyDict,
};
@ -153,19 +153,28 @@ fn init() -> PyResult<Driver> {
Ok(Driver(Some(rt_stop_tx)))
}
#[pyfunction]
fn get_default_config() -> crate::api::Config {
let mut conf = crate::api::Config::new("".to_string(), "".to_string());
conf.host = Some(conf.host().to_string());
conf.port = Some(conf.port());
conf.tls = Some(false);
conf
#[pymethods]
impl User {
#[getter]
fn get_id(&self) -> pyo3::PyResult<String> {
Ok(self.id.to_string())
}
#[setter]
fn set_id(&mut self, value: String) -> pyo3::PyResult<()> {
self.id = value.parse().map_err(|x: <uuid::Uuid as std::str::FromStr>::Err| pyo3::exceptions::PyRuntimeError::new_err(x.to_string()))?;
Ok(())
}
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl Config {
#[new]
#[pyo3(signature = (*, username, password, **kwds))]
#[pyo3(signature = (username, password, **kwds))]
pub fn pynew(
username: String,
password: String,
@ -176,7 +185,7 @@ impl Config {
let port = kwgs.get_item("port")?.and_then(|e| e.extract().ok());
let tls = kwgs.get_item("tls")?.and_then(|e| e.extract().ok());
Ok(Config {
Ok(Self {
username,
password,
host,
@ -184,9 +193,109 @@ impl Config {
tls,
})
} else {
Ok(Config::new(username, password))
Ok(Self::new(username, password))
}
}
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl Cursor {
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl Selection {
#[new]
#[pyo3(signature = (**kwds))]
pub fn py_new(kwds: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
if let Some(kwds) = kwds {
let start_row = if let Some(e) = kwds.get_item("start_row")? {
e.extract()?
} else {
0
};
let start_col = if let Some(e) = kwds.get_item("start_col")? {
e.extract()?
} else {
0
};
let end_row = if let Some(e) = kwds.get_item("end_row")? {
e.extract()?
} else {
0
};
let end_col = if let Some(e) = kwds.get_item("end_col")? {
e.extract()?
} else {
0
};
let buffer = if let Some(e) = kwds.get_item("buffer")? {
e.extract()?
} else {
String::default()
};
Ok(Self { start_row, start_col, end_row, end_col, buffer })
} else {
Ok(Self::default())
}
}
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl BufferUpdate {
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl TextChange {
#[new]
#[pyo3(signature = (**kwds))]
pub fn py_new(kwds: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
if let Some(kwds) = kwds {
let start = if let Some(e) = kwds.get_item("start")? {
e.extract()?
} else {
0
};
let end = if let Some(e) = kwds.get_item("end")? {
e.extract()?
} else {
0
};
let content = if let Some(e) = kwds.get_item("content")? {
e.extract()?
} else {
String::default()
};
Ok(Self { start, end, content })
} else {
Ok(Self::default())
}
}
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pyfunction]
@ -254,27 +363,24 @@ impl From<crate::errors::ControllerError> for PyErr {
}
}
impl IntoPy<PyObject> for crate::api::User {
fn into_py(self, py: Python<'_>) -> PyObject {
self.id.to_string().into_py(py)
}
}
#[pymodule]
fn codemp(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(version, m)?)?;
m.add_function(wrap_pyfunction!(init, m)?)?;
m.add_function(wrap_pyfunction!(get_default_config, m)?)?;
m.add_function(wrap_pyfunction!(connect, m)?)?;
m.add_function(wrap_pyfunction!(set_logger, m)?)?;
m.add_class::<Driver>()?;
m.add_class::<BufferUpdate>()?;
m.add_class::<TextChange>()?;
m.add_class::<BufferController>()?;
m.add_class::<Cursor>()?;
m.add_class::<Selection>()?;
m.add_class::<CursorController>()?;
m.add_class::<User>()?;
m.add_class::<Workspace>()?;
m.add_class::<Client>()?;
m.add_class::<Config>()?;