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 +13 -0
- memry/__pycache__/__init__.cpython-311.pyc +0 -0
- memry/__pycache__/client.cpython-311.pyc +0 -0
- memry/__pycache__/exceptions.cpython-311.pyc +0 -0
- memry/__pycache__/server.cpython-311.pyc +0 -0
- memry/client.py +148 -0
- memry/exceptions.py +11 -0
- memry/server.py +60 -0
- memry-0.1.0.data/scripts/memryd.exe +0 -0
- memry-0.1.0.dist-info/METADATA +155 -0
- memry-0.1.0.dist-info/RECORD +13 -0
- memry-0.1.0.dist-info/WHEEL +4 -0
- memry-0.1.0.dist-info/licenses/LICENSE +21 -0
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
|
|
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
|
+

|
|
21
|
+

|
|
22
|
+

|
|
23
|
+
[](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,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.
|