Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 332 additions & 2 deletions foreign/python/apache_iggy.pyi

Large diffs are not rendered by default.

103 changes: 99 additions & 4 deletions foreign/python/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use crate::consumer::{
IggyConsumer, py_delta_to_iggy_duration,
};
use crate::identifier::PyIdentifier;
use crate::permissions::Permissions as PyPermissions;
use crate::receive_message::{PollingStrategy, ReceiveMessage};
use crate::send_message::SendMessage;
use crate::stream::StreamDetails;
Expand Down Expand Up @@ -171,33 +172,36 @@ impl IggyClient {

/// Create a new user.
///
/// The user is created without permissions.
///
/// Args:
/// username: Username as `str`.
/// password: Password as `str`.
/// status: User status as `UserStatus | None`; defaults to `UserStatus.Active`.
/// permissions: Permissions as `Permissions | None`; the user has none when `None`.
///
/// Returns:
/// An awaitable that resolves to the created `UserInfoDetails`.
///
/// Raises:
/// PyRuntimeError: If an argument is invalid or the request fails.
#[pyo3(signature = (username, password, status=None))]
#[pyo3(signature = (username, password, status=None, permissions=None))]
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[UserInfoDetails]", imports=("collections.abc")))]
fn create_user<'a>(
&self,
py: Python<'a>,
username: String,
password: String,
#[gen_stub(override_type(type_repr = "UserStatus | None"))] status: Option<PyUserStatus>,
#[gen_stub(override_type(type_repr = "Permissions | None"))] permissions: Option<
PyPermissions,
>,
) -> PyResult<Bound<'a, PyAny>> {
let status = status.map_or(UserStatus::Active, UserStatus::from);
let permissions = permissions.map(|permissions| permissions.inner);
let inner = self.inner.clone();

future_into_py(py, async move {
let user = inner
.create_user(&username, &password, status, None)
.create_user(&username, &password, status, permissions)
.await
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
Ok(PyUserInfoDetails::from(user))
Expand Down Expand Up @@ -264,6 +268,97 @@ impl IggyClient {
})
}

/// Update the permissions of a user by unique ID or username.
///
/// This is a full replacement: the given permissions overwrite the previous
/// ones, and `None` removes them entirely.
///
/// Args:
/// user_id: User identifier as `str | int`.
/// permissions: New permissions as `Permissions | None`.
///
/// Returns:
/// An awaitable that resolves to `None` when the permissions are updated.
///
/// Raises:
/// PyValueError: If a string identifier is invalid.
/// PyRuntimeError: If the request fails.
#[pyo3(signature = (user_id, permissions))]
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]", imports=("collections.abc")))]
fn update_permissions<'a>(
&self,
py: Python<'a>,
user_id: PyIdentifier,
#[gen_stub(override_type(type_repr = "Permissions | None"))] permissions: Option<
PyPermissions,
>,
) -> PyResult<Bound<'a, PyAny>> {
let user_id = Identifier::try_from(user_id)?;
let permissions = permissions.map(|permissions| permissions.inner);
let inner = self.inner.clone();

future_into_py(py, async move {
inner
.update_permissions(&user_id, permissions)
.await
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
Ok(())
})
}

/// Change the password of a user by unique ID or username.
///
/// Args:
/// user_id: User identifier as `str | int`.
/// current_password: Current password as `str`.
/// new_password: New password as `str`.
///
/// Returns:
/// An awaitable that resolves to `None` when the password is changed.
///
/// Raises:
/// PyValueError: If a string identifier is invalid.
/// PyRuntimeError: If the current password is wrong or the request fails.
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]", imports=("collections.abc")))]
fn change_password<'a>(
&self,
py: Python<'a>,
user_id: PyIdentifier,
current_password: String,
new_password: String,
) -> PyResult<Bound<'a, PyAny>> {
let user_id = Identifier::try_from(user_id)?;
let inner = self.inner.clone();

future_into_py(py, async move {
inner
.change_password(&user_id, &current_password, &new_password)
.await
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
Ok(())
})
}

/// Log out the currently authenticated user.
///
/// Returns:
/// An awaitable that resolves to `None` when the user is logged out.
///
/// Raises:
/// PyRuntimeError: If the request fails.
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]", imports=("collections.abc")))]
fn logout_user<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
let inner = self.inner.clone();

future_into_py(py, async move {
inner
.logout_user()
.await
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
Ok(())
})
}

/// Connects the IggyClient to its service.
/// Returns Ok(()) on successful connection or a PyRuntimeError on failure.
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]", imports=("collections.abc")))]
Expand Down
6 changes: 6 additions & 0 deletions foreign/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
pub mod client;
mod consumer;
mod identifier;
mod permissions;
mod receive_message;
mod send_message;
mod stream;
Expand All @@ -29,6 +30,7 @@ use consumer::{
AutoCommit, AutoCommitAfter, AutoCommitWhen, ConsumerGroup, ConsumerGroupDetails,
ConsumerGroupMember, IggyConsumer, ReceiveMessageIterator,
};
use permissions::{GlobalPermissions, Permissions, StreamPermissions, TopicPermissions};
use pyo3::prelude::*;
use receive_message::{PollingStrategy, ReceiveMessage};
use send_message::SendMessage;
Expand Down Expand Up @@ -57,5 +59,9 @@ fn apache_iggy(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<UserStatus>()?;
m.add_class::<UserInfo>()?;
m.add_class::<UserInfoDetails>()?;
m.add_class::<Permissions>()?;
m.add_class::<GlobalPermissions>()?;
m.add_class::<StreamPermissions>()?;
m.add_class::<TopicPermissions>()?;
Ok(())
}
Loading
Loading