wse-server 2.3.0__tar.gz → 2.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {wse_server-2.3.0 → wse_server-2.3.1}/PKG-INFO +1 -1
  2. {wse_server-2.3.0 → wse_server-2.3.1}/pyproject.toml +1 -1
  3. {wse_server-2.3.0 → wse_server-2.3.1}/rust/Cargo.lock +1 -1
  4. {wse_server-2.3.0 → wse_server-2.3.1}/rust/Cargo.toml +1 -1
  5. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/server.rs +78 -2
  6. {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/__init__.py +1 -1
  7. {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/_wse_accel.pyi +11 -0
  8. {wse_server-2.3.0 → wse_server-2.3.1}/LICENSE +0 -0
  9. {wse_server-2.3.0 → wse_server-2.3.1}/README.md +0 -0
  10. {wse_server-2.3.0 → wse_server-2.3.1}/rust/.cargo/config.toml +0 -0
  11. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/jwt.rs +0 -0
  12. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/lib.rs +0 -0
  13. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/cluster.rs +0 -0
  14. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/compression.rs +0 -0
  15. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/filters.rs +0 -0
  16. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/mod.rs +0 -0
  17. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/presence.rs +0 -0
  18. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/queue.rs +0 -0
  19. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/rate_limiter.rs +0 -0
  20. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/recovery.rs +0 -0
  21. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/reliability.rs +0 -0
  22. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/security.rs +0 -0
  23. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/sequencer.rs +0 -0
  24. {wse_server-2.3.0 → wse_server-2.3.1}/rust/src/wse/transformer.rs +0 -0
  25. {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/core/__init__.py +0 -0
  26. {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/core/filters.py +0 -0
  27. {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/core/types.py +0 -0
  28. {wse_server-2.3.0 → wse_server-2.3.1}/wse_server/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wse-server
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "wse-server"
7
- version = "2.3.0"
7
+ version = "2.3.1"
8
8
  description = "Rust-powered WebSocket engine for Python. Up to 5M fan-out del/s. Zero-GIL JWT."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -2224,7 +2224,7 @@ dependencies = [
2224
2224
 
2225
2225
  [[package]]
2226
2226
  name = "wse-accel"
2227
- version = "2.3.0"
2227
+ version = "2.3.1"
2228
2228
  dependencies = [
2229
2229
  "aes-gcm",
2230
2230
  "ahash",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "wse-accel"
3
- version = "2.3.0"
3
+ version = "2.3.1"
4
4
  edition = "2024"
5
5
 
6
6
  [lib]
@@ -2421,6 +2421,11 @@ pub struct RustWSEServer {
2421
2421
 
2422
2422
  #[pymethods]
2423
2423
  impl RustWSEServer {
2424
+ /// Create a new WSE server instance.
2425
+ ///
2426
+ /// The server runs all transport in Rust (TCP, TLS, WS framing, JWT auth,
2427
+ /// compression, encryption, ping/pong). Python handles application logic
2428
+ /// via drain mode or callbacks.
2424
2429
  #[new]
2425
2430
  #[pyo3(signature = (host, port, max_connections = 1000, jwt_secret = None, jwt_issuer = None, jwt_audience = None, jwt_cookie_name = None, jwt_previous_secret = None, jwt_key_id = None, jwt_algorithm = None, jwt_private_key = None, max_inbound_queue_size = 131072, recovery_enabled = false, recovery_buffer_size = 128, recovery_ttl = 300, recovery_max_messages = 500, recovery_memory_budget = 268435456, presence_enabled = false, presence_max_data_size = 4096, presence_max_members = 0, rate_limit_capacity = 100_000.0, rate_limit_refill = 10_000.0, max_message_size = 1_048_576, ping_interval = 25, idle_timeout = 60, max_outbound_queue_bytes = 16_777_216))]
2426
2431
  #[allow(clippy::too_many_arguments)]
@@ -2596,6 +2601,13 @@ impl RustWSEServer {
2596
2601
  })
2597
2602
  }
2598
2603
 
2604
+ /// Register Python callbacks for connection events.
2605
+ ///
2606
+ /// - on_connect(conn_id, cookies) -- called when a client connects
2607
+ /// - on_message(conn_id, data) -- called when a message is received
2608
+ /// - on_disconnect(conn_id) -- called when a client disconnects
2609
+ ///
2610
+ /// Alternative to drain mode. Cannot be used together with enable_drain_mode().
2599
2611
  fn set_callbacks(
2600
2612
  &self,
2601
2613
  py: Python<'_>,
@@ -2616,6 +2628,10 @@ impl RustWSEServer {
2616
2628
  Ok(())
2617
2629
  }
2618
2630
 
2631
+ /// Start the WebSocket server on a background thread.
2632
+ ///
2633
+ /// Spawns a dedicated tokio runtime, binds to host:port, and begins
2634
+ /// accepting connections. Raises RuntimeError if already running.
2619
2635
  fn start(&mut self) -> PyResult<()> {
2620
2636
  if self.running.load(Ordering::SeqCst) {
2621
2637
  return Err(PyRuntimeError::new_err("Server is already running"));
@@ -3119,6 +3135,10 @@ impl RustWSEServer {
3119
3135
  Ok(())
3120
3136
  }
3121
3137
 
3138
+ /// Stop the server and close all connections.
3139
+ ///
3140
+ /// Shuts down the cluster mesh, sends a Shutdown command, and joins
3141
+ /// the server thread. Safe to call if not running (returns Ok).
3122
3142
  fn stop(&mut self, py: Python<'_>) -> PyResult<()> {
3123
3143
  if !self.running.load(Ordering::SeqCst) {
3124
3144
  return Ok(());
@@ -3146,6 +3166,7 @@ impl RustWSEServer {
3146
3166
 
3147
3167
  // -- send methods -------------------------------------------------------
3148
3168
 
3169
+ /// Send a raw text frame to a single connection.
3149
3170
  fn send(&self, conn_id: &str, data: &str) -> PyResult<()> {
3150
3171
  let tx = self
3151
3172
  .cmd_tx
@@ -3159,6 +3180,7 @@ impl RustWSEServer {
3159
3180
  Ok(())
3160
3181
  }
3161
3182
 
3183
+ /// Send a raw binary frame to a single connection.
3162
3184
  fn send_bytes(&self, conn_id: &str, data: &[u8]) -> PyResult<()> {
3163
3185
  let tx = self
3164
3186
  .cmd_tx
@@ -3427,6 +3449,7 @@ impl RustWSEServer {
3427
3449
  Ok(byte_count)
3428
3450
  }
3429
3451
 
3452
+ /// Send a text frame to all connected clients (no topic filtering).
3430
3453
  fn broadcast_all(&self, data: &str) -> PyResult<()> {
3431
3454
  let tx = self
3432
3455
  .cmd_tx
@@ -3438,6 +3461,7 @@ impl RustWSEServer {
3438
3461
  Ok(())
3439
3462
  }
3440
3463
 
3464
+ /// Send a binary frame to all connected clients.
3441
3465
  fn broadcast_all_bytes(&self, data: &[u8]) -> PyResult<()> {
3442
3466
  let tx = self
3443
3467
  .cmd_tx
@@ -3452,10 +3476,12 @@ impl RustWSEServer {
3452
3476
 
3453
3477
  // -- query / management -------------------------------------------------
3454
3478
 
3479
+ /// Number of active connections (lock-free AtomicUsize read).
3455
3480
  fn get_connection_count(&self) -> usize {
3456
3481
  self.shared.connection_count.load(Ordering::Relaxed)
3457
3482
  }
3458
3483
 
3484
+ /// List all active connection IDs.
3459
3485
  fn get_connections(&self) -> PyResult<Vec<String>> {
3460
3486
  let tx = self
3461
3487
  .cmd_tx
@@ -3469,6 +3495,7 @@ impl RustWSEServer {
3469
3495
  .map_err(|_| PyRuntimeError::new_err("Reply dropped"))
3470
3496
  }
3471
3497
 
3498
+ /// Force-disconnect a connection by sending a Close frame.
3472
3499
  fn disconnect(&self, conn_id: &str) -> PyResult<()> {
3473
3500
  let tx = self
3474
3501
  .cmd_tx
@@ -3481,6 +3508,7 @@ impl RustWSEServer {
3481
3508
  Ok(())
3482
3509
  }
3483
3510
 
3511
+ /// Return True if the server is running.
3484
3512
  fn is_running(&self) -> bool {
3485
3513
  self.running.load(Ordering::SeqCst)
3486
3514
  }
@@ -3497,9 +3525,12 @@ impl RustWSEServer {
3497
3525
  /// Drain up to max_count inbound events. Blocks up to timeout_ms waiting
3498
3526
  /// for at least one event. Returns list of (event_type, conn_id, data) tuples.
3499
3527
  ///
3500
- /// event_type: "connect" | "auth_connect" | "msg" | "raw" | "bin" | "disconnect"
3528
+ /// event_type: "connect" | "auth_connect" | "msg" | "raw" | "bin" |
3529
+ /// "disconnect" | "presence_join" | "presence_leave"
3530
+ ///
3501
3531
  /// data: str (for "connect"=cookies, "auth_connect"=user_id, "raw"),
3502
- /// dict (for "msg"), bytes (for "bin"), None (for "disconnect")
3532
+ /// dict (for "msg", "presence_join", "presence_leave"),
3533
+ /// bytes (for "bin"), None (for "disconnect")
3503
3534
  ///
3504
3535
  /// GIL is released while waiting on channel, acquired once for batch conversion.
3505
3536
  #[pyo3(signature = (max_count = 256, timeout_ms = 50))]
@@ -3649,16 +3680,26 @@ impl RustWSEServer {
3649
3680
  Ok(list.unbind())
3650
3681
  }
3651
3682
 
3683
+ /// Number of inbound events dropped due to queue overflow.
3652
3684
  fn inbound_dropped_count(&self) -> u64 {
3653
3685
  self.shared.inbound_dropped.load(Ordering::Relaxed)
3654
3686
  }
3655
3687
 
3688
+ /// Current number of events waiting in the inbound queue.
3656
3689
  fn inbound_queue_depth(&self) -> usize {
3657
3690
  self.shared.inbound_rx.len()
3658
3691
  }
3659
3692
 
3660
3693
  // -- Cluster Protocol -----------------------------------------------------
3661
3694
 
3695
+ /// Connect to cluster peers via TCP mesh protocol.
3696
+ ///
3697
+ /// Two modes: static peers (pass peer addresses directly) or seed-based
3698
+ /// discovery (pass seeds + cluster_addr for gossip). TLS is optional;
3699
+ /// falls back to WSE_CLUSTER_TLS_CERT/KEY/CA env vars if not provided.
3700
+ ///
3701
+ /// Raises RuntimeError if cluster is already connected or if seeds
3702
+ /// is used without cluster_addr.
3662
3703
  #[allow(clippy::too_many_arguments)]
3663
3704
  #[pyo3(signature = (peers, tls_cert=None, tls_key=None, tls_ca=None, cluster_port=None, seeds=None, cluster_addr=None))]
3664
3705
  fn connect_cluster(
@@ -3781,6 +3822,7 @@ impl RustWSEServer {
3781
3822
  Ok(())
3782
3823
  }
3783
3824
 
3825
+ /// Return True if at least one cluster peer is connected.
3784
3826
  fn cluster_connected(&self) -> bool {
3785
3827
  self.shared
3786
3828
  .cluster_metrics
@@ -3789,6 +3831,7 @@ impl RustWSEServer {
3789
3831
  > 0
3790
3832
  }
3791
3833
 
3834
+ /// Number of connected cluster peers.
3792
3835
  fn cluster_peers_count(&self) -> u64 {
3793
3836
  self.shared
3794
3837
  .cluster_metrics
@@ -3819,6 +3862,8 @@ impl RustWSEServer {
3819
3862
  Ok(())
3820
3863
  }
3821
3864
 
3865
+ /// List connected cluster peers with address, instance_id, capabilities,
3866
+ /// and connected_at timestamp. Returns a list of dicts.
3822
3867
  fn cluster_info(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
3823
3868
  let list = PyList::empty(py);
3824
3869
  for entry in self.shared.cluster_metrics.peer_info.iter() {
@@ -3849,6 +3894,11 @@ impl RustWSEServer {
3849
3894
 
3850
3895
  // -- Topic subscriptions --------------------------------------------------
3851
3896
 
3897
+ /// Subscribe a connection to topics.
3898
+ ///
3899
+ /// Optionally set initial presence data (requires presence_enabled=True).
3900
+ /// If queue_group is set, the connection joins a named group for
3901
+ /// round-robin dispatch instead of standard fan-out.
3852
3902
  #[pyo3(signature = (conn_id, topics, presence_data = None, queue_group = None))]
3853
3903
  fn subscribe_connection(
3854
3904
  &self,
@@ -4014,6 +4064,13 @@ impl RustWSEServer {
4014
4064
  Ok(())
4015
4065
  }
4016
4066
 
4067
+ /// Subscribe with message recovery support.
4068
+ ///
4069
+ /// Returns a dict with per-topic recovery status:
4070
+ /// {recovered: bool, topics: {topic: {epoch, offset, recoverable, recovered, count}}}
4071
+ ///
4072
+ /// When recover=True, replays missed messages from the recovery buffer.
4073
+ /// Pass epoch (hex string) and offset from a previous session to resume.
4017
4074
  #[pyo3(signature = (conn_id, topics, recover = false, epoch = None, offset = None))]
4018
4075
  fn subscribe_with_recovery(
4019
4076
  &self,
@@ -4149,6 +4206,8 @@ impl RustWSEServer {
4149
4206
  Ok(result_dict.into())
4150
4207
  }
4151
4208
 
4209
+ /// Unsubscribe a connection from topics.
4210
+ /// If topics is None, unsubscribe from all topics.
4152
4211
  #[pyo3(signature = (conn_id, topics = None))]
4153
4212
  fn unsubscribe_connection(&self, conn_id: &str, topics: Option<Vec<String>>) -> PyResult<()> {
4154
4213
  // Acquire refcount lock first to keep subscription + refcount atomic
@@ -4351,6 +4410,7 @@ impl RustWSEServer {
4351
4410
  Ok(())
4352
4411
  }
4353
4412
 
4413
+ /// Number of fan-out subscribers on a topic (excludes queue group members).
4354
4414
  fn get_topic_subscriber_count(&self, topic: &str) -> usize {
4355
4415
  self.shared
4356
4416
  .topic_subscribers
@@ -4370,6 +4430,13 @@ impl RustWSEServer {
4370
4430
  Ok(dict.into())
4371
4431
  }
4372
4432
 
4433
+ /// Full server health snapshot as a dict.
4434
+ ///
4435
+ /// Keys: connections, inbound_queue_depth, inbound_dropped,
4436
+ /// cluster_connected, cluster_peer_count, cluster_messages_sent/delivered/dropped,
4437
+ /// cluster_bytes_sent/received, cluster_reconnect_count, cluster_dlq_size,
4438
+ /// uptime_secs, recovery_enabled/topic_count/total_bytes,
4439
+ /// presence_enabled/topics/total_users.
4373
4440
  fn health_snapshot(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
4374
4441
  let dict = PyDict::new(py);
4375
4442
  dict.set_item(
@@ -4451,6 +4518,11 @@ impl RustWSEServer {
4451
4518
  Ok(dict.unbind())
4452
4519
  }
4453
4520
 
4521
+ /// Return Prometheus exposition format metrics string.
4522
+ ///
4523
+ /// Includes gauges (connections, queue depth, uptime, drops),
4524
+ /// per-topic counters (top 50 by volume), cluster metrics,
4525
+ /// and optional recovery/presence gauges.
4454
4526
  fn prometheus_metrics(&self) -> String {
4455
4527
  let mut out = String::with_capacity(4096);
4456
4528
 
@@ -4680,6 +4752,10 @@ impl RustWSEServer {
4680
4752
  out
4681
4753
  }
4682
4754
 
4755
+ /// Drain and return dead-letter-queue entries from failed cluster sends.
4756
+ ///
4757
+ /// Each entry is a dict with keys: topic, payload, peer_addr, error.
4758
+ /// Entries are consumed (not peeked) -- calling twice returns different results.
4683
4759
  fn get_cluster_dlq_entries(&self, py: Python<'_>) -> PyResult<Py<PyList>> {
4684
4760
  let entries = match self.shared.cluster_dlq.try_lock() {
4685
4761
  Ok(mut dlq) => dlq.drain_all(),
@@ -26,7 +26,7 @@ from wse_server._wse_accel import (
26
26
  from wse_server.core.filters import EventFilter
27
27
  from wse_server.core.types import DeliveryGuarantee, EventPriority
28
28
 
29
- __version__ = "2.3.0"
29
+ __version__ = "2.3.1"
30
30
 
31
31
  __all__ = [
32
32
  "RustWSEServer",
@@ -522,6 +522,17 @@ class RustWSEServer:
522
522
  def health_snapshot(self) -> dict[str, Any]:
523
523
  """Full server health metrics: connections, queue, cluster, recovery, presence, uptime."""
524
524
  ...
525
+ def prometheus_metrics(self) -> str:
526
+ """Return Prometheus exposition format metrics string.
527
+
528
+ Gauges: wse_connections, wse_inbound_queue_depth, wse_uptime_seconds,
529
+ wse_slow_consumer_drops, wse_inbound_dropped.
530
+ Per-topic: wse_topic_messages_total{topic="..."} (top 50 by count).
531
+ Cluster: wse_cluster_peers, wse_cluster_dlq_size.
532
+ Recovery: wse_recovery_topics, wse_recovery_bytes.
533
+ Presence: wse_presence_topics, wse_presence_users.
534
+ """
535
+ ...
525
536
  def cluster_info(self) -> list[dict[str, Any]]:
526
537
  """List connected cluster peers with address, instance_id, capabilities, connected_at."""
527
538
  ...
File without changes
File without changes
File without changes
File without changes