Skip to content
Merged
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
1 change: 1 addition & 0 deletions BinaryOptionsToolsV2/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions BinaryOptionsToolsV2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ use pocketoption::{RawPocketOption, RawStreamIterator, StreamIterator, RawHandle
use pyo3::prelude::*;
use validator::RawValidator;

use crate::pocketoption::RawHandlerRust;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The removal of RawHandlerRust import is consistent with the refactoring in pocketoption.rs where RawHandlerRust was renamed and split into RawHandle and RawHandler.


#[pymodule(name = "BinaryOptionsToolsV2")]
fn BinaryOptionsTools(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<StreamLogsIterator>()?;
Expand All @@ -26,8 +24,10 @@ fn BinaryOptionsTools(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<StreamIterator>()?;
m.add_class::<RawStreamIterator>()?;
m.add_class::<RawValidator>()?;
m.add_class::<RawHandlerRust>()?;
// m.add_class::<PyConfig>()?;
m.add_class::<RawHandle>()?;
m.add_class::<RawHandler>()?;
m.add_class::<RawHandle>()?;
m.add_class::<RawHandler>()?;
Comment on lines +27 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove duplicate class registrations.

RawHandle and RawHandler are each registered twice. This appears to be a leftover from the conflict resolution mentioned in the PR description. Remove the duplicate lines.

Proposed fix
     m.add_class::<RawHandle>()?;
     m.add_class::<RawHandler>()?;
-    m.add_class::<RawHandle>()?;
-    m.add_class::<RawHandler>()?;
🤖 Prompt for AI Agents
In `@BinaryOptionsToolsV2/src/lib.rs` around lines 27 - 30, The module registers
RawHandle and RawHandler twice; remove the duplicate m.add_class::<RawHandle>()?
and m.add_class::<RawHandler>()? lines so each class is only registered once in
the module initialization (leave a single m.add_class::<RawHandle>()? and a
single m.add_class::<RawHandler>()? call where the other duplicates currently
appear). Ensure the remaining calls are the ones used in the module setup where
m.add_class is invoked.


m.add_function(wrap_pyfunction!(start_tracing, m)?)?;
Ok(())
Expand Down
108 changes: 6 additions & 102 deletions BinaryOptionsToolsV2/src/pocketoption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,109 +82,13 @@ pub struct RawStreamIterator {
}

#[pyclass]
pub struct RawHandlerRust {
handler: Arc<Mutex<binary_options_tools::pocketoption::modules::raw::RawHandler>>,
pub struct RawHandle {
handle: binary_options_tools::pocketoption::modules::raw::RawHandle,
}

#[pymethods]
impl RawHandlerRust {
/// Send a text message through this handler
pub fn send_text<'py>(&self, py: Python<'py>, message: String) -> PyResult<Bound<'py, PyAny>> {
let handler = self.handler.clone();
future_into_py(py, async move {
handler
.lock()
.await
.send_text(message)
.await
.map_err(BinaryErrorPy::from)?;
Ok(())
})
}

/// Send a binary message through this handler
pub fn send_binary<'py>(&self, py: Python<'py>, data: Vec<u8>) -> PyResult<Bound<'py, PyAny>> {
let handler = self.handler.clone();
future_into_py(py, async move {
handler
.lock()
.await
.send_binary(data)
.await
.map_err(BinaryErrorPy::from)?;
Ok(())
})
}

/// Send a message and wait for the next matching response
pub fn send_and_wait<'py>(
&self,
py: Python<'py>,
message: String,
) -> PyResult<Bound<'py, PyAny>> {
let handler = self.handler.clone();
future_into_py(py, async move {
let outgoing =
binary_options_tools::pocketoption::modules::raw::Outgoing::Text(message);
let response = handler
.lock()
.await
.send_and_wait(outgoing)
.await
.map_err(BinaryErrorPy::from)?;
let msg_str = response.to_text().unwrap_or_default().to_string();
Python::attach(|py| msg_str.into_py_any(py))
})
}

/// Wait for the next matching message
pub fn wait_next<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let handler = self.handler.clone();
future_into_py(py, async move {
let response = handler
.lock()
.await
.wait_next()
.await
.map_err(BinaryErrorPy::from)?;
let msg_str = response.to_text().unwrap_or_default().to_string();
Python::attach(|py| msg_str.into_py_any(py))
})
}

/// Subscribe to messages matching this handler's validator
pub fn subscribe<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let handler = self.handler.clone();
future_into_py(py, async move {
let receiver = {
let handler_guard = handler.lock().await;
handler_guard.subscribe()
};

// Create a boxed stream that yields String values
let boxed_stream = async_stream::stream! {
while let Ok(msg) = receiver.recv().await {
let msg_str = msg.to_text().unwrap_or_default().to_string();
yield Ok(msg_str);
}
}
.boxed()
.fuse();

let stream = Arc::new(Mutex::new(boxed_stream));
Python::attach(|py| RawStreamIterator { stream }.into_py_any(py))
})
}

/// Get the handler's unique ID
pub fn id(&self, py: Python<'_>) -> PyResult<String> {
let runtime = get_runtime(py)?;
let handler = self.handler.clone();
runtime.block_on(async move {
let handler_guard = handler.lock().await;
Ok(handler_guard.id().to_string())
})
}
#[pyclass]
pub struct RawHandler {
handler: Arc<Mutex<binary_options_tools::pocketoption::modules::raw::RawHandler>>,
}

#[pymethods]
Expand Down Expand Up @@ -745,7 +649,7 @@ impl RawPocketOption {
.await
.map_err(BinaryErrorPy::from)?;
Python::attach(|py| {
RawHandlerRust {
RawHandler {
handler: Arc::new(Mutex::new(handler)),
}
.into_py_any(py)
Expand Down
38 changes: 38 additions & 0 deletions BinaryOptionsToolsV2/tests/reproduce_race.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import asyncio
import os
import sys
from BinaryOptionsToolsV2 import PocketOption

# Mock SSID (won't connect effectively but allows object creation)
SSID = r'42["auth",{"session":"mock_session","isDemo":1,"uid":12345,"platform":1}]'

async def trade_task(api, asset, amount, time, task_id):
print(f"Task {task_id}: Starting trade...")
try:
# buying usually returns a tuple (uuid, deal)
result = await api.buy(asset, amount, time)
print(f"Task {task_id}: Trade completed: {result}")
except Exception as e:
print(f"Task {task_id}: Trade failed: {e}")

async def main():
# This test assumes we can mock the connection or at least instantiate the client
# Without a live server or extensive mocking, this script is illustrative.
# However, if we could run it, it would hang.

try:
api = await PocketOption(SSID)
except Exception as e:
print(f"Failed to init api (expected if no connection): {e}")
return

# Simulate two concurrent trades
task1 = asyncio.create_task(trade_task(api, "EURUSD_otc", 1.0, 60, 1))
task2 = asyncio.create_task(trade_task(api, "EURUSD_otc", 1.0, 60, 2))

await asyncio.gather(task1, task2)

await api.disconnect()

if __name__ == "__main__":
asyncio.run(main())
Loading