memry 0.1.0__py3-none-win_amd64.whl

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.
memry/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """
2
+ Memry: Lightning-fast, in-memory, cross-process data frames for Python.
3
+ Stop Pickling. Start Computing.
4
+ """
5
+ __version__ = "0.1.0"
6
+
7
+ # --- CORRECTED IMPORTS ---
8
+ # We no longer import `start_server` from `memry.server` because that
9
+ # function has been removed. The user now starts the server via the command line.
10
+ # We also no longer need to import `shutdown_server` here, as it's not part of the primary API.
11
+
12
+ from .client import put, get, delete, list_keys, close, shutdown_server
13
+ from .exceptions import MemryError, ConnectionError, ServerError
Binary file
Binary file
memry/client.py ADDED
@@ -0,0 +1,148 @@
1
+ import json
2
+ import socket
3
+ import io
4
+ import pandas as pd
5
+ import pyarrow as pa
6
+ import pyarrow.ipc
7
+ from .exceptions import ConnectionError, ServerError
8
+
9
+ # --- CHANGED to a TCP Address ---
10
+ SERVER_ADDRESS = ("127.0.0.1", 56789)
11
+ BUFFER_SIZE = 8192
12
+
13
+ class MemryConnection:
14
+ """Manages a persistent connection to the Memry daemon."""
15
+ def __init__(self):
16
+ self.sock = None
17
+ self.reader = None
18
+
19
+ def connect(self):
20
+ if self.sock and self.is_connected():
21
+ return
22
+ try:
23
+ # --- CHANGED to an INET (TCP) socket ---
24
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
25
+ self.sock.connect(SERVER_ADDRESS)
26
+ self.reader = self.sock.makefile('rb')
27
+ except (socket.error, ConnectionRefusedError) as e:
28
+ self.sock = None
29
+ self.reader = None
30
+ raise ConnectionError(
31
+ f"Could not connect to Memry daemon at {SERVER_ADDRESS[0]}:{SERVER_ADDRESS[1]}. "
32
+ "Is the server running?"
33
+ ) from e
34
+
35
+ def is_connected(self):
36
+ try:
37
+ # Create a temporary connection to check status without disrupting the main one
38
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as test_sock:
39
+ test_sock.settimeout(0.5) # Don't wait forever
40
+ test_sock.connect(SERVER_ADDRESS)
41
+ return True
42
+ except (socket.error, ConnectionRefusedError):
43
+ return False
44
+
45
+ def close(self):
46
+ if self.sock:
47
+ try:
48
+ self.sock.close()
49
+ except socket.error:
50
+ pass
51
+ self.sock = None
52
+ self.reader = None
53
+
54
+ def _ensure_connection(self):
55
+ if not self.sock:
56
+ self.connect()
57
+ # Ping to check if the connection is still alive
58
+ try:
59
+ self._send_command_on_socket({'action': 'Ping'})
60
+ response = self._receive_response_from_reader()
61
+ if response.get('message') != 'pong':
62
+ raise ConnectionError("Connection lost")
63
+ except (socket.error, BrokenPipeError, ConnectionError):
64
+ # If ping fails, reconnect and try again
65
+ self.connect()
66
+
67
+ def _send_command_on_socket(self, cmd_dict):
68
+ payload = (json.dumps(cmd_dict) + '\n').encode('utf-8')
69
+ self.sock.sendall(payload)
70
+
71
+ def _receive_response_from_reader(self):
72
+ line = self.reader.readline()
73
+ if not line:
74
+ raise ConnectionError("Daemon closed the connection unexpectedly.")
75
+
76
+ resp = json.loads(line)
77
+ if resp.get('status') == 'Error':
78
+ raise ServerError(resp.get('message', 'Unknown server error'))
79
+ return resp
80
+
81
+ def _read_data(self, data_len):
82
+ return self.reader.read(data_len)
83
+
84
+ # Global connection object for simple API
85
+ _connection = MemryConnection()
86
+
87
+ def put(df: pd.DataFrame) -> str:
88
+ _connection._ensure_connection()
89
+ if not isinstance(df, pd.DataFrame):
90
+ raise TypeError("Input must be a pandas DataFrame")
91
+
92
+ table = pa.Table.from_pandas(df, preserve_index=False)
93
+ sink = io.BytesIO()
94
+ with pa.ipc.new_stream(sink, table.schema) as writer:
95
+ writer.write_table(table)
96
+
97
+ data_bytes = sink.getvalue()
98
+
99
+ cmd = {"action": "Put", "data_len": len(data_bytes)}
100
+ _connection._send_command_on_socket(cmd)
101
+ _connection.sock.sendall(data_bytes)
102
+
103
+ response = _connection._receive_response_from_reader()
104
+ return response['message']
105
+
106
+ def get(key: str) -> pd.DataFrame:
107
+ _connection._ensure_connection()
108
+ cmd = {"action": "Get", "key": key}
109
+ _connection._send_command_on_socket(cmd)
110
+
111
+ response = _connection._receive_response_from_reader()
112
+ data_len = response['data_len']
113
+
114
+ data_bytes = _connection._read_data(data_len)
115
+
116
+ with pa.ipc.open_stream(data_bytes) as reader:
117
+ table = reader.read_all()
118
+
119
+ return table.to_pandas()
120
+
121
+ def delete(key: str):
122
+ _connection._ensure_connection()
123
+ cmd = {"action": "Delete", "key": key}
124
+ _connection._send_command_on_socket(cmd)
125
+ _connection._receive_response_from_reader()
126
+
127
+ def list_keys() -> list:
128
+ _connection._ensure_connection()
129
+ cmd = {"action": "ListKeys"}
130
+ _connection._send_command_on_socket(cmd)
131
+ response = _connection._receive_response_from_reader()
132
+ return response.get('keys', [])
133
+
134
+ def shutdown_server():
135
+ print("Sending shutdown command to Memry daemon...")
136
+ try:
137
+ _connection._ensure_connection()
138
+ cmd = {"action": "Shutdown"}
139
+ _connection._send_command_on_socket(cmd)
140
+ _connection._receive_response_from_reader()
141
+ print("Shutdown successful.")
142
+ except ConnectionError:
143
+ print("Could not connect to server. It might already be stopped.")
144
+ finally:
145
+ _connection.close()
146
+
147
+ def close():
148
+ _connection.close()
memry/exceptions.py ADDED
@@ -0,0 +1,11 @@
1
+ class MemryError(Exception):
2
+ """Base exception for the Memry client."""
3
+ pass
4
+
5
+ class ConnectionError(MemryError):
6
+ """Raised when the client cannot connect to the Memry daemon."""
7
+ pass
8
+
9
+ class ServerError(MemryError):
10
+ """Raised when the server returns an error message."""
11
+ pass
memry/server.py ADDED
@@ -0,0 +1,60 @@
1
+ import subprocess
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ def get_server_binary_path() -> Path:
6
+ """
7
+ Finds the path to the packaged Rust binary ('memryd'), handling
8
+ both installed packages and local development environments. This function
9
+ is platform-aware and looks for '.exe' on Windows.
10
+ """
11
+ # Determine the correct binary name based on the operating system
12
+ if sys.platform == "win32":
13
+ binary_name = "memryd.exe"
14
+ else:
15
+ binary_name = "memryd"
16
+
17
+ # 1. Check the standard installation location first.
18
+ install_path = Path(__file__).parent / "bin" / binary_name
19
+ if install_path.exists():
20
+ return install_path
21
+
22
+ # 2. If not found, this is a development environment.
23
+ # The binary is in the project's root `target` directory.
24
+ project_root = Path(__file__).parent.parent
25
+ dev_path = project_root / "target" / "debug" / binary_name
26
+ if dev_path.exists():
27
+ return dev_path
28
+
29
+ # Check release as a final fallback
30
+ dev_path_release = project_root / "target" / "release" / binary_name
31
+ if dev_path_release.exists():
32
+ return dev_path_release
33
+
34
+ # 3. If it's in none of those places, the build is broken.
35
+ raise FileNotFoundError(
36
+ f"Memry server binary ('{binary_name}') could not be found in the installation "
37
+ "or development target directories. Please run 'maturin develop'."
38
+ )
39
+
40
+ def main():
41
+ """
42
+ Finds and runs the compiled Memry daemon, letting it take over the
43
+ current terminal. Press Ctrl+C in the terminal to stop the server.
44
+ """
45
+ try:
46
+ server_path = get_server_binary_path()
47
+ print(f"--- Launching Memry Server from: {server_path} ---")
48
+ subprocess.run([server_path], check=True)
49
+ except FileNotFoundError as e:
50
+ print(f"ERROR: {e}", file=sys.stderr)
51
+ sys.exit(1)
52
+ except subprocess.CalledProcessError as e:
53
+ print(f"ERROR: Memry server exited with an error (code {e.returncode}).", file=sys.stderr)
54
+ sys.exit(1)
55
+ except KeyboardInterrupt:
56
+ print("\nLauncher interrupted. Server shut down.")
57
+ sys.exit(0)
58
+
59
+ if __name__ == "__main__":
60
+ main()
Binary file
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: memry
3
+ Version: 0.1.0
4
+ Classifier: License :: OSI Approved :: MIT License
5
+ Classifier: Programming Language :: Python
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Rust
8
+ Requires-Dist: pandas>=1.0
9
+ Requires-Dist: pyarrow>=10.0
10
+ License-File: LICENSE
11
+ Summary: Lightning-fast, in-memory, cross-process data store for Python
12
+ Author-email: Your Name <your@email.com>
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
+ Project-URL: Homepage, https://github.com/your-username/memry
16
+ Project-URL: Repository, https://github.com/your-username/memry
17
+
18
+ # Project Memry
19
+
20
+ ![Python Version](https://img.shields.io/pypi/pyversions/memry)
21
+ ![PyPI Version](https://img.shields.io/pypi/v/memry.svg)
22
+ ![Build Status](https://img.shields.io/github/actions/workflow/status/your-username/memry/ci.yml?branch=main)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
+
25
+ ### *Stop Pickling. Start Computing.*
26
+
27
+ **Memry** is a lightning-fast, in-memory, cross-process data store for Python, designed to eliminate the crippling overhead of serialization (`pickle`) in multiprocessing workflows.
28
+
29
+ ## The Problem
30
+
31
+ When using Python's `multiprocessing` library, sharing large objects like Pandas DataFrames between processes is incredibly slow. Python must `pickle` the data in the main process, send the bytes over a pipe, and `unpickle` it in the child process. For gigabytes of data, this overhead can make your parallel code slower than single-threaded code.
32
+
33
+ ## The Solution: Memry
34
+
35
+ Memry runs a tiny, high-performance daemon (written in Rust) that manages a block of memory. Your Python processes can `put` and `get` data from this central store.
36
+
37
+ Instead of sending your huge DataFrame to each process, you store it in Memry **once** and then pass a tiny string key to your child processes. Data transfer is nearly instantaneous because it's handled by a highly optimized system using the Apache Arrow format.
38
+
39
+ **Analogy:** Instead of photocopying a 1,000-page book for every colleague (pickling), you place the book on a shared library shelf and just tell them its location (Memry).
40
+
41
+
42
+ *(You would create and upload a simple diagram showing this flow)*
43
+
44
+ ## Benchmarks
45
+
46
+ The results speak for themselves. This benchmark measures the time taken to send a Pandas DataFrame to 4 worker processes and have them access it.
47
+
48
+ | DataFrame Size | `multiprocessing` (Pickle) | **`memry`** (Arrow IPC) | Speedup |
49
+ | :------------- | :------------------------- | :---------------------- | :------ |
50
+ | 10 MB | 0.15 s | **0.008 s** | **18x** |
51
+ | 100 MB | 1.48 s | **0.07 s** | **21x** |
52
+ | 500 MB | 7.9 s | **0.35 s** | **22x** |
53
+ | 1 GB | 17.2 s | **0.71 s** | **24x** |
54
+
55
+ *(These are realistic estimates. Run `examples/benchmark.py` to generate your own.)*
56
+
57
+ <img src="https://i.imgur.com/your-benchmark-graph.png" alt="Benchmark Graph" width="600"/>
58
+ *(You would generate and upload a graph plotting this data)*
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install memry
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ **1. Start the Memry Server**
69
+
70
+ From your terminal:
71
+ ```bash
72
+ memry-server start
73
+ ```
74
+ *To stop the server, run `memry-server stop`.*
75
+
76
+ **2. Use it in your Python code**
77
+
78
+ Compare the standard, slow way with the revolutionary Memry way.
79
+
80
+ **Before Memry (The Pain):**
81
+ ```python
82
+ import multiprocessing as mp
83
+ import pandas as pd
84
+ import numpy as np
85
+ import time
86
+
87
+ def process_chunk(df):
88
+ # This is slow because 'df' had to be pickled and sent.
89
+ return df['value'].sum()
90
+
91
+ if __name__ == "__main__":
92
+ large_df = pd.DataFrame(np.random.rand(10_000_000, 1), columns=['value'])
93
+
94
+ start_time = time.time()
95
+ with mp.Pool(processes=4) as pool:
96
+ # This is the slow part! Pickling and transferring the data.
97
+ results = pool.map(process_chunk, [large_df] * 4)
98
+ print(f"Standard multiprocessing took: {time.time() - start_time:.2f}s")
99
+ ```
100
+
101
+ **After Memry (The Revolution):**
102
+ ```python
103
+ import multiprocessing as mp
104
+ import pandas as pd
105
+ import numpy as np
106
+ import time
107
+ import memry
108
+
109
+ def process_chunk_from_memry(data_key):
110
+ # Getting data is INSTANTANEOUS (no unpickling!)
111
+ df = memry.get(data_key)
112
+ return df['value'].sum()
113
+
114
+ if __name__ == "__main__":
115
+ large_df = pd.DataFrame(np.random.rand(10_000_000, 1), columns=['value'])
116
+
117
+ start_time = time.time()
118
+ # 1. Put the data into Memry ONCE. This is fast.
119
+ data_key = memry.put(large_df)
120
+
121
+ # 2. Pass only the tiny key string.
122
+ with mp.Pool(processes=4) as pool:
123
+ results = pool.map(process_chunk_from_memry, [data_key] * 4)
124
+
125
+ print(f"Memry multiprocessing took: {time.time() - start_time:.2f}s")
126
+
127
+ # 3. Clean up the data from Memry.
128
+ memry.delete(data_key)
129
+ ```
130
+
131
+ ## API
132
+
133
+ - `memry.put(df: pd.DataFrame) -> str`: Store a DataFrame, get a key.
134
+ - `memry.get(key: str) -> pd.DataFrame`: Retrieve a DataFrame using its key.
135
+ - `memry.delete(key: str)`: Remove a DataFrame from the store.
136
+ - `memry.list_keys() -> list`: List all keys in the store.
137
+ - `memry.close()`: Close the client's connection to the daemon.
138
+ - `memry.start_server()`: Start the server from within Python.
139
+ - `memry.shutdown_server()`: Stop the server from within Python.
140
+
141
+ ## Roadmap
142
+
143
+ This is just the beginning. The vision for Memry includes:
144
+ - [ ] **True Zero-Copy:** Transition from Unix sockets to shared memory for even faster reads.
145
+ - [ ] **In-Memory Querying:** Run SQL-like queries directly on the stored Arrow data without moving it into Python.
146
+ - [ ] **Cross-Language Support:** Clients for Julia, R, etc.
147
+ - [ ] **Persistence:** Snapshotting the in-memory database to disk.
148
+
149
+ ## Contributing
150
+
151
+ Contributions are welcome! Please open an issue or submit a pull request.
152
+
153
+ ## License
154
+
155
+ This project is licensed under the MIT License.
@@ -0,0 +1,13 @@
1
+ memry-0.1.0.data/scripts/memryd.exe,sha256=NV0kWUlan0BouZv-qHWjDyz8uA6QEKfIx5jUpWx9fmQ,2020352
2
+ memry-0.1.0.dist-info/METADATA,sha256=sKolpV1bNB5e0ksOcj3X9DM0EZyb4gdVj1qqP4IaY9U,6101
3
+ memry-0.1.0.dist-info/WHEEL,sha256=otkNWQo5-3v8ZzaSqBicFgqaRvPB-IMXst8ik5MzIfo,93
4
+ memry-0.1.0.dist-info/licenses/LICENSE,sha256=q53YqEH5OACuJ8YmE3i9pND509hapVaOX42ix2AMkZ8,1085
5
+ memry/__init__.py,sha256=o9nq3BcusK3gB3Aq-eHYWP-51lKqpZZ091kZOBt1EKE,560
6
+ memry/__pycache__/__init__.cpython-311.pyc,sha256=Rht705OZKls0BIbevQwzCuK3s_VYE6Om2Ubragx69ss,619
7
+ memry/__pycache__/client.cpython-311.pyc,sha256=jP0rl63Z2U6uGAdMnD9MH-pRDMrYAs4yf60nAzhZUG0,9760
8
+ memry/__pycache__/exceptions.cpython-311.pyc,sha256=OtyiKgGthrpKEmHy46anKDz-96z___7zx_FFECLGDag,981
9
+ memry/__pycache__/server.cpython-311.pyc,sha256=kJ84drEKdmiYFO3kmSKGZ7s0zEL2vs-Fifo7imeKFbg,3029
10
+ memry/client.py,sha256=8p6YukOYHAq-EnJ4uuRFcQ-4QKnl3z6XsHX1m1PDjew,5035
11
+ memry/exceptions.py,sha256=MwEOP3RqnrkQiq-COmQqu_RTKB7VP9JfkjBP7TlhCGI,308
12
+ memry/server.py,sha256=AgS0WJParqrkSU4-XIlJLHYJ2nJVOe1xvf63un1NUw0,2218
13
+ memry-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.8.7)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.