montycat 0.1.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.
- montycat-0.1.1/PKG-INFO +37 -0
- montycat-0.1.1/README.md +22 -0
- montycat-0.1.1/montycat/__init__.py +4 -0
- montycat-0.1.1/montycat/core/__init__.py +0 -0
- montycat-0.1.1/montycat/core/engine.py +101 -0
- montycat-0.1.1/montycat/core/limit.py +7 -0
- montycat-0.1.1/montycat/core/schema.py +75 -0
- montycat-0.1.1/montycat/core/store.py +33 -0
- montycat-0.1.1/montycat/store_classes/__init__.py +0 -0
- montycat-0.1.1/montycat/store_classes/kv.py +221 -0
- montycat-0.1.1/montycat/store_functions/__init__.py +0 -0
- montycat-0.1.1/montycat/store_functions/store_generic_functions.py +123 -0
- montycat-0.1.1/montycat.egg-info/PKG-INFO +37 -0
- montycat-0.1.1/montycat.egg-info/SOURCES.txt +19 -0
- montycat-0.1.1/montycat.egg-info/dependency_links.txt +1 -0
- montycat-0.1.1/montycat.egg-info/not-zip-safe +1 -0
- montycat-0.1.1/montycat.egg-info/requires.txt +3 -0
- montycat-0.1.1/montycat.egg-info/top_level.txt +1 -0
- montycat-0.1.1/pyproject.toml +4 -0
- montycat-0.1.1/setup.cfg +4 -0
- montycat-0.1.1/setup.py +22 -0
montycat-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: montycat
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A Python client for MontyCat, NoSQL store utilizing Data Mesh architecture.
|
|
5
|
+
Author: MontyGovernance
|
|
6
|
+
Author-email: eugene.and.monty@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: orjson
|
|
13
|
+
Requires-Dist: xxhash
|
|
14
|
+
Requires-Dist: asyncio
|
|
15
|
+
|
|
16
|
+
# MontyCat
|
|
17
|
+
|
|
18
|
+
**MontyCat** is a Python client for **MontyCat**, a high-performance, distributed NoSQL store designed with a cutting-edge Data Mesh architecture. This client empowers developers to seamlessly manage and query their data while leveraging the unparalleled flexibility and scalability offered by NoSQL databases within a decentralized data ownership paradigm.
|
|
19
|
+
|
|
20
|
+
## Key Features
|
|
21
|
+
|
|
22
|
+
- **High Performance:** Harnesses MontyCat's advanced architecture for lightning-fast read and write operations.
|
|
23
|
+
- **Distributed Architecture:** Effortlessly connects to a distributed NoSQL database, enabling dynamic horizontal scalability across diverse data domains.
|
|
24
|
+
- **Data Mesh Design:** Innovatively utilizes a Data Mesh framework that empowers cross-functional teams to own, manage, and serve their own data, enhancing collaboration and eliminating bottlenecks.
|
|
25
|
+
- **Asynchronous Support:** Built on `asyncio` for ultra-responsive, non-blocking operations, enabling high concurrency and real-time data processing.
|
|
26
|
+
- **Meshing and Sharding:** Efficiently orchestrates data across multiple nodes with robust support for data meshing and sharding, optimizing resource utilization.
|
|
27
|
+
- **Memory Safety:** Implements state-of-the-art memory management practices, minimizing the risk of memory-related issues and enhancing stability.
|
|
28
|
+
- **Smart Data Governance:** Integrates intelligent governance features to ensure data quality and compliance across the distributed architecture.
|
|
29
|
+
- **Seamless Integration:** Offers a simple and intuitive API for effortless integration with your Python applications, reducing time-to-value.
|
|
30
|
+
- **Robust Documentation:** Comprehensive and user-friendly documentation to accelerate onboarding and maximize productivity.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
You can install MontyCat using pip:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install montycat
|
montycat-0.1.1/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# MontyCat
|
|
2
|
+
|
|
3
|
+
**MontyCat** is a Python client for **MontyCat**, a high-performance, distributed NoSQL store designed with a cutting-edge Data Mesh architecture. This client empowers developers to seamlessly manage and query their data while leveraging the unparalleled flexibility and scalability offered by NoSQL databases within a decentralized data ownership paradigm.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **High Performance:** Harnesses MontyCat's advanced architecture for lightning-fast read and write operations.
|
|
8
|
+
- **Distributed Architecture:** Effortlessly connects to a distributed NoSQL database, enabling dynamic horizontal scalability across diverse data domains.
|
|
9
|
+
- **Data Mesh Design:** Innovatively utilizes a Data Mesh framework that empowers cross-functional teams to own, manage, and serve their own data, enhancing collaboration and eliminating bottlenecks.
|
|
10
|
+
- **Asynchronous Support:** Built on `asyncio` for ultra-responsive, non-blocking operations, enabling high concurrency and real-time data processing.
|
|
11
|
+
- **Meshing and Sharding:** Efficiently orchestrates data across multiple nodes with robust support for data meshing and sharding, optimizing resource utilization.
|
|
12
|
+
- **Memory Safety:** Implements state-of-the-art memory management practices, minimizing the risk of memory-related issues and enhancing stability.
|
|
13
|
+
- **Smart Data Governance:** Integrates intelligent governance features to ensure data quality and compliance across the distributed architecture.
|
|
14
|
+
- **Seamless Integration:** Offers a simple and intuitive API for effortless integration with your Python applications, reducing time-to-value.
|
|
15
|
+
- **Robust Documentation:** Comprehensive and user-friendly documentation to accelerate onboarding and maximize productivity.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
You can install MontyCat using pip:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install montycat
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import orjson
|
|
3
|
+
|
|
4
|
+
class Engine:
|
|
5
|
+
def __init__(self, host: str, port: int, username: str, password: str, store_name: str):
|
|
6
|
+
self.host = host
|
|
7
|
+
self.port = port
|
|
8
|
+
self.username = username
|
|
9
|
+
self.password = password
|
|
10
|
+
self.store_name = store_name
|
|
11
|
+
|
|
12
|
+
async def send_data(host: str, port: int, string: str):
|
|
13
|
+
resp = None
|
|
14
|
+
writer = None
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
reader, writer = await asyncio.open_connection(host, port)
|
|
18
|
+
writer.write(string + b"\n")
|
|
19
|
+
await writer.drain()
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
resp = await asyncio.wait_for(reader.readuntil(b"\n"), timeout=120)
|
|
23
|
+
resp = resp.decode().strip()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
resp = recursive_parse_orjson(resp)
|
|
27
|
+
except Exception as parse_error:
|
|
28
|
+
print(f"Failed to parse response: {parse_error}")
|
|
29
|
+
|
|
30
|
+
except asyncio.TimeoutError:
|
|
31
|
+
resp = "Operation timed out"
|
|
32
|
+
|
|
33
|
+
except ConnectionRefusedError:
|
|
34
|
+
resp = "Connection refused"
|
|
35
|
+
except Exception as e:
|
|
36
|
+
resp = f"An unexpected error occurred: {e}"
|
|
37
|
+
finally:
|
|
38
|
+
if writer:
|
|
39
|
+
writer.close()
|
|
40
|
+
await writer.wait_closed()
|
|
41
|
+
return resp
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def recursive_parse_orjson(data):
|
|
45
|
+
"""
|
|
46
|
+
Recursively parses nested JSON strings in the provided data using orjson for faster parsing.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
data: A Python object that may contain JSON strings, including nested structures.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A fully parsed Python object with all nested JSON strings converted.
|
|
53
|
+
"""
|
|
54
|
+
if isinstance(data, dict):
|
|
55
|
+
return {
|
|
56
|
+
key: recursive_parse_orjson(value)
|
|
57
|
+
for key, value in data.items()
|
|
58
|
+
}
|
|
59
|
+
elif isinstance(data, tuple):
|
|
60
|
+
return tuple(recursive_parse_orjson(element) if not element.isdigit() else element for element in data)
|
|
61
|
+
elif isinstance(data, list):
|
|
62
|
+
return [recursive_parse_orjson(element) if not element.isdigit() else element for element in data]
|
|
63
|
+
elif isinstance(data, str):
|
|
64
|
+
try:
|
|
65
|
+
parsed_data = orjson.loads(data)
|
|
66
|
+
return recursive_parse_orjson(parsed_data)
|
|
67
|
+
except orjson.JSONDecodeError:
|
|
68
|
+
return data
|
|
69
|
+
|
|
70
|
+
elif isinstance(data, (int, float)):
|
|
71
|
+
return data
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
# def recursive_parse_orjson(data):
|
|
77
|
+
# """
|
|
78
|
+
# Recursively parses nested JSON strings in the provided data using orjson for faster parsing.
|
|
79
|
+
|
|
80
|
+
# Args:
|
|
81
|
+
# data: A Python object that may contain JSON strings, including nested structures.
|
|
82
|
+
|
|
83
|
+
# Returns:
|
|
84
|
+
# A fully parsed Python object with all nested JSON strings converted.
|
|
85
|
+
# """
|
|
86
|
+
# if isinstance(data, dict):
|
|
87
|
+
# return {key: recursive_parse_orjson(value) for key, value in data.items()}
|
|
88
|
+
# elif isinstance(data, tuple):
|
|
89
|
+
# return tuple(recursive_parse_orjson(element) for element in data)
|
|
90
|
+
# elif isinstance(data, list):
|
|
91
|
+
# return [recursive_parse_orjson(element) for element in data]
|
|
92
|
+
# elif isinstance(data, str):
|
|
93
|
+
# try:
|
|
94
|
+
# # Attempt to parse the string as JSON
|
|
95
|
+
# parsed_data = orjson.loads(data)
|
|
96
|
+
# return recursive_parse_orjson(parsed_data)
|
|
97
|
+
# except orjson.JSONDecodeError:
|
|
98
|
+
# # Return the original string if it can't be parsed
|
|
99
|
+
# return data
|
|
100
|
+
# else:
|
|
101
|
+
# return data
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import get_origin, get_args, get_type_hints, Union
|
|
2
|
+
|
|
3
|
+
class Pointer:
|
|
4
|
+
def __init__(self, **kwargs):
|
|
5
|
+
|
|
6
|
+
for key, value in kwargs.items():
|
|
7
|
+
setattr(self, key, [value[0].store_namespace, value[1]])
|
|
8
|
+
|
|
9
|
+
def __str__(self):
|
|
10
|
+
return str(self.__dict__)
|
|
11
|
+
|
|
12
|
+
def serialize(self):
|
|
13
|
+
return self.__dict__
|
|
14
|
+
|
|
15
|
+
class Schema:
|
|
16
|
+
def __init__(self, **kwargs):
|
|
17
|
+
hints = get_type_hints(self.__class__)
|
|
18
|
+
for key, value in kwargs.items():
|
|
19
|
+
setattr(self, key, value)
|
|
20
|
+
for attribute in hints:
|
|
21
|
+
if not hasattr(self, attribute):
|
|
22
|
+
setattr(self, attribute, None)
|
|
23
|
+
self.validate_types()
|
|
24
|
+
|
|
25
|
+
def __repr__(self):
|
|
26
|
+
return str(self.__dict__)
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
return str(self.__dict__)
|
|
30
|
+
|
|
31
|
+
def serialize(self):
|
|
32
|
+
if hasattr(self, "pointers"):
|
|
33
|
+
self.pointers = self.pointers.serialize()
|
|
34
|
+
for k, v in self.pointers.items():
|
|
35
|
+
if v[1].isdigit():
|
|
36
|
+
self.pointers[k] = [v[0].store_namespace, str(v[1])]
|
|
37
|
+
|
|
38
|
+
return self.__dict__
|
|
39
|
+
|
|
40
|
+
def validate_types(self):
|
|
41
|
+
hints = get_type_hints(self.__class__)
|
|
42
|
+
for attribute, expected_type in hints.items():
|
|
43
|
+
actual_value = getattr(self, attribute)
|
|
44
|
+
|
|
45
|
+
origin = get_origin(expected_type)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if origin is Union:
|
|
49
|
+
|
|
50
|
+
expected_types = get_args(expected_type)
|
|
51
|
+
if not isinstance(actual_value, expected_types) and actual_value is not None:
|
|
52
|
+
raise TypeError(
|
|
53
|
+
f"Attribute '{attribute}' should be one of types {expected_types}, "
|
|
54
|
+
f"but got '{type(actual_value).__name__}'"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
elif origin is Pointer:
|
|
58
|
+
if not isinstance(actual_value, Pointer) and actual_value is not None:
|
|
59
|
+
raise TypeError(
|
|
60
|
+
f"Attribute '{attribute}' should be of type '{expected_type.__name__}', "
|
|
61
|
+
f"but got '{type(actual_value).__name__}'"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
elif origin is None:
|
|
65
|
+
if not isinstance(actual_value, expected_type) and actual_value is not None:
|
|
66
|
+
raise TypeError(
|
|
67
|
+
f"Attribute '{attribute}' should be of type '{expected_type.__name__}', "
|
|
68
|
+
f"but got '{type(actual_value).__name__}'"
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
if not isinstance(actual_value, origin) and actual_value is not None:
|
|
72
|
+
raise TypeError(
|
|
73
|
+
f"Attribute '{attribute}' should be of type '{expected_type}', "
|
|
74
|
+
f"but got '{type(actual_value).__name__}'"
|
|
75
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from ..store_classes.kv import generic_kv
|
|
2
|
+
|
|
3
|
+
class Store:
|
|
4
|
+
class InMemory(generic_kv):
|
|
5
|
+
persistent = False
|
|
6
|
+
distributed = False
|
|
7
|
+
|
|
8
|
+
def __init__(self, *args, **kwargs):
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
class Distributed(generic_kv):
|
|
12
|
+
persistent = False
|
|
13
|
+
distributed = True
|
|
14
|
+
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
super().__init__(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
class Persistent(generic_kv):
|
|
19
|
+
persistent = True
|
|
20
|
+
distributed = False
|
|
21
|
+
|
|
22
|
+
def __init__(self, *args, **kwargs):
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
class Distributed(generic_kv):
|
|
26
|
+
persistent = True
|
|
27
|
+
distributed = True
|
|
28
|
+
|
|
29
|
+
def __init__(self, *args, **kwargs):
|
|
30
|
+
super().__init__(*args, **kwargs)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
File without changes
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from ..core.engine import Engine, send_data
|
|
2
|
+
from ..core.schema import Pointer
|
|
3
|
+
from ..store_functions.store_generic_functions import handle_limit, connect_engine_, create_namespace_, drop_namespace_, drop_store_, show_store_properties_, convert_to_binary_query, convert_custom_key, convert_custom_keys, convert_custom_keys_values
|
|
4
|
+
import asyncio
|
|
5
|
+
|
|
6
|
+
class generic_kv:
|
|
7
|
+
store_name: str = ""
|
|
8
|
+
command: str = ""
|
|
9
|
+
persistent: bool = False
|
|
10
|
+
request: str = "store"
|
|
11
|
+
blockchain: bool = False
|
|
12
|
+
limit_output: dict = {}
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def _run_query(cls, query: str):
|
|
16
|
+
return asyncio.run(send_data(cls.host, cls.port, query))
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def insert_custom_key(cls, custom_key: str, expire_sec: int = 0):
|
|
20
|
+
"""
|
|
21
|
+
Args:
|
|
22
|
+
custom_key: A custom key to insert into the store. This key can be used to retrieve the value later.
|
|
23
|
+
expire_sec: The number of seconds before the inserted value expires.
|
|
24
|
+
Returns:
|
|
25
|
+
True if the insert operation was successful. Class 'str' if the insert operation failed.
|
|
26
|
+
"""
|
|
27
|
+
custom_key_converted = convert_custom_key(custom_key)
|
|
28
|
+
cls.command = "insert_custom_key"
|
|
29
|
+
query = convert_to_binary_query(cls, key=custom_key_converted, expire_sec=expire_sec)
|
|
30
|
+
return cls._run_query(query)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def insert_custom_key_value(cls, custom_key: str, value: dict, expire_sec: int = 0):
|
|
34
|
+
"""
|
|
35
|
+
Args:
|
|
36
|
+
custom_key: A custom key to insert into the store. This key can be used to retrieve the value later.
|
|
37
|
+
value: A Python class / dict to insert into the store.
|
|
38
|
+
expire_sec: The number of seconds before the inserted value expires.
|
|
39
|
+
Returns:
|
|
40
|
+
True if the insert operation was successful. Class 'str' if the insert operation failed.
|
|
41
|
+
|
|
42
|
+
custom_key = "some_value"
|
|
43
|
+
value = {
|
|
44
|
+
"operation": 1,
|
|
45
|
+
"amount": 4500.0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
custom_key_converted = convert_custom_key(custom_key)
|
|
50
|
+
cls.command = "insert_custom_key_value"
|
|
51
|
+
query = convert_to_binary_query(cls, key=custom_key_converted, value=value, expire_sec=expire_sec)
|
|
52
|
+
return cls._run_query(query)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def insert_value(cls, value: dict, expire_sec: int = 0):
|
|
56
|
+
"""
|
|
57
|
+
Args:
|
|
58
|
+
value: A Python class / dict to insert into the store.
|
|
59
|
+
expire_sec: The number of seconds before the inserted value expires.
|
|
60
|
+
Returns:
|
|
61
|
+
Key number if the insert operation was successful. Class 'str' if the insert operation failed.
|
|
62
|
+
"""
|
|
63
|
+
cls.command = "insert_value"
|
|
64
|
+
query = convert_to_binary_query(cls, value=value, expire_sec=expire_sec)
|
|
65
|
+
return cls._run_query(query)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_value(cls, key: int | str = "", custom_key: str = "", with_pointers: bool = False):
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
Args:
|
|
72
|
+
key: The key number of the value to retrieve.
|
|
73
|
+
custom_key: The custom key of the value to retrieve.
|
|
74
|
+
with_pointers: A boolean value that determines whether to include pointers (foreign values) in the output.
|
|
75
|
+
Returns:
|
|
76
|
+
The value associated with the key or custom key. Class 'str' if the get operation failed.
|
|
77
|
+
"""
|
|
78
|
+
if len(custom_key) > 0:
|
|
79
|
+
key = convert_custom_key(custom_key)
|
|
80
|
+
cls.command = "get_value"
|
|
81
|
+
query = convert_to_binary_query(cls, key=key, with_pointers=with_pointers)
|
|
82
|
+
return cls._run_query(query)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def get_keys(cls, limit: list = []):
|
|
86
|
+
"""
|
|
87
|
+
Args:
|
|
88
|
+
Limit: A list of two integers that determine the range of keys to retrieve.
|
|
89
|
+
Example: limit = [10, 20] will retrieve keys 10 to 20.
|
|
90
|
+
Returns:
|
|
91
|
+
A list of keys in the store. Class 'str' if the get operation failed.
|
|
92
|
+
"""
|
|
93
|
+
lim = handle_limit(limit)
|
|
94
|
+
cls.command = "get_keys"
|
|
95
|
+
cls.limit_output = lim
|
|
96
|
+
query = convert_to_binary_query(cls)
|
|
97
|
+
return cls._run_query(query)
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def delete_key(cls, key: int | str = "", custom_key: str = ""):
|
|
101
|
+
|
|
102
|
+
if len(custom_key) > 0:
|
|
103
|
+
key = convert_custom_key(custom_key)
|
|
104
|
+
|
|
105
|
+
cls.command = "delete_key"
|
|
106
|
+
query = convert_to_binary_query(cls, key=key)
|
|
107
|
+
return cls._run_query(query)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def update_value(cls, key: int | str = "", custom_key: str = "", **filters):
|
|
111
|
+
|
|
112
|
+
if len(custom_key) > 0:
|
|
113
|
+
key = convert_custom_key(custom_key)
|
|
114
|
+
|
|
115
|
+
cls.command = "update_value"
|
|
116
|
+
query = convert_to_binary_query(cls, key=key, value=filters)
|
|
117
|
+
print(query)
|
|
118
|
+
return cls._run_query(query)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def insert_bulk(cls, bulk_values: list, expire_sec: int = 0):
|
|
122
|
+
# bulk_values = [str(item) for item in bulk if len(bulk) > 0]
|
|
123
|
+
"""
|
|
124
|
+
Args:
|
|
125
|
+
bulk_values: A list of Python objects to insert into the store.
|
|
126
|
+
expire_sec: The number of seconds before the inserted values expire.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if the bulk insert operation was successful.
|
|
130
|
+
List of values that were not inserted.
|
|
131
|
+
"""
|
|
132
|
+
cls.command = "insert_bulk"
|
|
133
|
+
query = convert_to_binary_query(cls, bulk_values=bulk_values, expire_sec=expire_sec)
|
|
134
|
+
return cls._run_query(query)
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def delete_bulk(cls, bulk_keys: list = [], bulk_custom_keys: list = []):
|
|
138
|
+
|
|
139
|
+
if len(bulk_custom_keys) > 0:
|
|
140
|
+
bulk_custom_keys = convert_custom_keys(bulk_custom_keys)
|
|
141
|
+
bulk_keys += bulk_custom_keys
|
|
142
|
+
|
|
143
|
+
cls.command = "delete_bulk"
|
|
144
|
+
query = convert_to_binary_query(cls, bulk_keys=bulk_keys)
|
|
145
|
+
return cls._run_query(query)
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def get_bulk(cls, bulk_keys: list = [], bulk_custom_keys: list = [], limit: list = [], with_pointers: bool = False):
|
|
149
|
+
|
|
150
|
+
lim = handle_limit(limit)
|
|
151
|
+
|
|
152
|
+
if len(bulk_custom_keys) > 0:
|
|
153
|
+
bulk_custom_keys = convert_custom_keys(bulk_custom_keys)
|
|
154
|
+
bulk_keys += bulk_custom_keys
|
|
155
|
+
|
|
156
|
+
cls.command = "get_bulk"
|
|
157
|
+
cls.limit_output = lim
|
|
158
|
+
query = convert_to_binary_query(cls, bulk_keys=bulk_keys, with_pointers=with_pointers)
|
|
159
|
+
return cls._run_query(query)
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def update_bulk(cls, bulk_keys_values: dict = {}, bulk_custom_keys_values: dict = {}):
|
|
163
|
+
|
|
164
|
+
if len(bulk_custom_keys_values) > 0:
|
|
165
|
+
bulk_custom_keys_values = convert_custom_keys_values(bulk_custom_keys_values)
|
|
166
|
+
bulk_keys_values = bulk_keys_values | bulk_custom_keys_values
|
|
167
|
+
|
|
168
|
+
# print(bulk_keys_values)
|
|
169
|
+
|
|
170
|
+
cls.command = "update_bulk"
|
|
171
|
+
query = convert_to_binary_query(cls, bulk_keys_values=bulk_keys_values)
|
|
172
|
+
return cls._run_query(query)
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def lookup_keys_where(cls, limit: int = 0, **filters):
|
|
176
|
+
|
|
177
|
+
lim = handle_limit(limit)
|
|
178
|
+
|
|
179
|
+
cls.command = "lookup_keys"
|
|
180
|
+
cls.limit_output = lim
|
|
181
|
+
query = convert_to_binary_query(cls, search_criteria=filters)
|
|
182
|
+
return cls._run_query(query)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def lookup_values_where(cls, limit = 0, with_pointers: bool = False, **filters):
|
|
186
|
+
|
|
187
|
+
lim = handle_limit(limit)
|
|
188
|
+
|
|
189
|
+
cls.command = "lookup_values"
|
|
190
|
+
cls.limit_output = lim
|
|
191
|
+
query = convert_to_binary_query(cls, search_criteria=filters, with_pointers=with_pointers)
|
|
192
|
+
return cls._run_query(query)
|
|
193
|
+
|
|
194
|
+
@classmethod
|
|
195
|
+
def to_blockchain(cls, key: int):
|
|
196
|
+
cls.command = "blockchain"
|
|
197
|
+
cls.persistent = True
|
|
198
|
+
cls.blockchain = True
|
|
199
|
+
query = convert_to_binary_query(cls, key=key)
|
|
200
|
+
return cls._run_query(query)
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def connect_engine(cls, engine: Engine) -> None:
|
|
204
|
+
connect_engine_(cls, engine)
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def create_namespace(cls):
|
|
208
|
+
return create_namespace_(cls)
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def drop_namespace(cls):
|
|
212
|
+
return drop_namespace_(cls)
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def drop_store(cls):
|
|
216
|
+
drop_store_(cls)
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def show_store_properties(cls):
|
|
220
|
+
show_store_properties_(cls)
|
|
221
|
+
|
|
File without changes
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from ..core.engine import Engine, send_data
|
|
2
|
+
from ..core.limit import Limit
|
|
3
|
+
from ..core.schema import Pointer
|
|
4
|
+
import asyncio
|
|
5
|
+
import orjson
|
|
6
|
+
import xxhash
|
|
7
|
+
|
|
8
|
+
def connect_engine_(cls: type, engine: Engine) -> None:
|
|
9
|
+
cls.username = engine.username
|
|
10
|
+
cls.password = engine.password
|
|
11
|
+
cls.host = engine.host
|
|
12
|
+
cls.port = engine.port
|
|
13
|
+
cls.store_name = engine.store_name
|
|
14
|
+
|
|
15
|
+
def convert_custom_key(key: int | str) -> int:
|
|
16
|
+
return str(xxhash.xxh32(str(key)).intdigest())
|
|
17
|
+
|
|
18
|
+
def convert_custom_keys(keys: list) -> list:
|
|
19
|
+
return [convert_custom_key(key) for key in keys]
|
|
20
|
+
|
|
21
|
+
def convert_custom_keys_values(keys_values: dict) -> dict:
|
|
22
|
+
return {convert_custom_key(key): value for key, value in keys_values.items()}
|
|
23
|
+
|
|
24
|
+
def modify_pointers(value: dict):
|
|
25
|
+
if "pointers" in value:
|
|
26
|
+
try:
|
|
27
|
+
for k, v in value['pointers'].items():
|
|
28
|
+
if v[1].isdigit():
|
|
29
|
+
value['pointers'][k] = [v[0], str(v[1])]
|
|
30
|
+
else:
|
|
31
|
+
value['pointers'][k] = [v[0], convert_custom_key(v[1])]
|
|
32
|
+
return value
|
|
33
|
+
except:
|
|
34
|
+
raise ValueError("Pointer should be a valid hash key or custom key")
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def convert_to_binary_query(
|
|
40
|
+
cls: type,
|
|
41
|
+
key: str = "",
|
|
42
|
+
search_criteria: dict = {},
|
|
43
|
+
value: dict = {},
|
|
44
|
+
expire_sec: int = 0,
|
|
45
|
+
bulk_values: list = [],
|
|
46
|
+
bulk_keys: list = [],
|
|
47
|
+
bulk_keys_values: dict = {},
|
|
48
|
+
with_pointers: bool = False
|
|
49
|
+
):
|
|
50
|
+
|
|
51
|
+
if len(value) > 0:
|
|
52
|
+
value = modify_pointers(value)
|
|
53
|
+
|
|
54
|
+
if len(bulk_values) > 0:
|
|
55
|
+
bulk_values = [str(modify_pointers(value)) for value in bulk_values]
|
|
56
|
+
|
|
57
|
+
if len(bulk_keys_values) > 0:
|
|
58
|
+
bulk_keys_values = {key: str(modify_pointers(value)) for key, value in bulk_keys_values.items()}
|
|
59
|
+
|
|
60
|
+
return orjson.dumps({
|
|
61
|
+
"request": cls.request,
|
|
62
|
+
"username": cls.username,
|
|
63
|
+
"password": cls.password,
|
|
64
|
+
"store_namespace": cls.store_namespace,
|
|
65
|
+
"store_name": cls.store_name,
|
|
66
|
+
"persistent": cls.persistent,
|
|
67
|
+
"distributed": cls.distributed,
|
|
68
|
+
"limit_output": cls.limit_output,
|
|
69
|
+
"key": str(key),
|
|
70
|
+
"value": str(value).replace("True", "true").replace("False", "false"),
|
|
71
|
+
"command": cls.command,
|
|
72
|
+
"expire": expire_sec,
|
|
73
|
+
"bulk_values": [str(value).replace("True", "true").replace("False", "false") for value in bulk_values],
|
|
74
|
+
"bulk_keys": bulk_keys,
|
|
75
|
+
"bulk_keys_values": {key: str(value).replace("True", "true").replace("False", "false") for key, value in bulk_keys_values.items()},
|
|
76
|
+
"blockchain": cls.blockchain,
|
|
77
|
+
"search_criteria": str(search_criteria).replace("True", "true").replace("False", "false"),
|
|
78
|
+
'with_pointers': with_pointers,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
def run_query(cls: type):
|
|
82
|
+
query = convert_to_binary_query(cls)
|
|
83
|
+
return asyncio.run(send_data(cls.host, cls.port, query))
|
|
84
|
+
|
|
85
|
+
def handle_limit(limit: list) -> dict:
|
|
86
|
+
|
|
87
|
+
limit_class = Limit()
|
|
88
|
+
|
|
89
|
+
if type(limit) == list:
|
|
90
|
+
if len(limit) > 1:
|
|
91
|
+
limit_class.start = limit[0]
|
|
92
|
+
limit_class.stop = limit[1]
|
|
93
|
+
elif len(limit) == 1:
|
|
94
|
+
limit_class.stop = limit[0]
|
|
95
|
+
|
|
96
|
+
return limit_class.return_limit()
|
|
97
|
+
|
|
98
|
+
def create_namespace_(cls: type) -> None:
|
|
99
|
+
cls.command = "create_namespace"
|
|
100
|
+
cls.request = "utils"
|
|
101
|
+
return run_query(cls)
|
|
102
|
+
|
|
103
|
+
def drop_namespace_(cls: type) -> None:
|
|
104
|
+
cls.command = "drop_namespace"
|
|
105
|
+
cls.request = "utils"
|
|
106
|
+
return run_query(cls)
|
|
107
|
+
|
|
108
|
+
def drop_store_(cls: type) -> None:
|
|
109
|
+
cls.command = "drop_store"
|
|
110
|
+
cls.request = "utils"
|
|
111
|
+
return print('DROP', cls.store_name)
|
|
112
|
+
|
|
113
|
+
def show_store_properties_(cls: type) -> None:
|
|
114
|
+
return print(
|
|
115
|
+
f"Store Name: {cls.store_name}\n"
|
|
116
|
+
f"Store Namespace: {cls.store_namespace}\n"
|
|
117
|
+
f"Persistent: {cls.persistent}\n"
|
|
118
|
+
f"Distributed: {cls.distributed}\n"
|
|
119
|
+
f"Host: {cls.host}\n"
|
|
120
|
+
f"Port: {cls.port}\n"
|
|
121
|
+
f"Username: {cls.username}\n"
|
|
122
|
+
f"Password: {cls.password}\n"
|
|
123
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: montycat
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A Python client for MontyCat, NoSQL store utilizing Data Mesh architecture.
|
|
5
|
+
Author: MontyGovernance
|
|
6
|
+
Author-email: eugene.and.monty@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: orjson
|
|
13
|
+
Requires-Dist: xxhash
|
|
14
|
+
Requires-Dist: asyncio
|
|
15
|
+
|
|
16
|
+
# MontyCat
|
|
17
|
+
|
|
18
|
+
**MontyCat** is a Python client for **MontyCat**, a high-performance, distributed NoSQL store designed with a cutting-edge Data Mesh architecture. This client empowers developers to seamlessly manage and query their data while leveraging the unparalleled flexibility and scalability offered by NoSQL databases within a decentralized data ownership paradigm.
|
|
19
|
+
|
|
20
|
+
## Key Features
|
|
21
|
+
|
|
22
|
+
- **High Performance:** Harnesses MontyCat's advanced architecture for lightning-fast read and write operations.
|
|
23
|
+
- **Distributed Architecture:** Effortlessly connects to a distributed NoSQL database, enabling dynamic horizontal scalability across diverse data domains.
|
|
24
|
+
- **Data Mesh Design:** Innovatively utilizes a Data Mesh framework that empowers cross-functional teams to own, manage, and serve their own data, enhancing collaboration and eliminating bottlenecks.
|
|
25
|
+
- **Asynchronous Support:** Built on `asyncio` for ultra-responsive, non-blocking operations, enabling high concurrency and real-time data processing.
|
|
26
|
+
- **Meshing and Sharding:** Efficiently orchestrates data across multiple nodes with robust support for data meshing and sharding, optimizing resource utilization.
|
|
27
|
+
- **Memory Safety:** Implements state-of-the-art memory management practices, minimizing the risk of memory-related issues and enhancing stability.
|
|
28
|
+
- **Smart Data Governance:** Integrates intelligent governance features to ensure data quality and compliance across the distributed architecture.
|
|
29
|
+
- **Seamless Integration:** Offers a simple and intuitive API for effortless integration with your Python applications, reducing time-to-value.
|
|
30
|
+
- **Robust Documentation:** Comprehensive and user-friendly documentation to accelerate onboarding and maximize productivity.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
You can install MontyCat using pip:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install montycat
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
montycat/__init__.py
|
|
5
|
+
montycat.egg-info/PKG-INFO
|
|
6
|
+
montycat.egg-info/SOURCES.txt
|
|
7
|
+
montycat.egg-info/dependency_links.txt
|
|
8
|
+
montycat.egg-info/not-zip-safe
|
|
9
|
+
montycat.egg-info/requires.txt
|
|
10
|
+
montycat.egg-info/top_level.txt
|
|
11
|
+
montycat/core/__init__.py
|
|
12
|
+
montycat/core/engine.py
|
|
13
|
+
montycat/core/limit.py
|
|
14
|
+
montycat/core/schema.py
|
|
15
|
+
montycat/core/store.py
|
|
16
|
+
montycat/store_classes/__init__.py
|
|
17
|
+
montycat/store_classes/kv.py
|
|
18
|
+
montycat/store_functions/__init__.py
|
|
19
|
+
montycat/store_functions/store_generic_functions.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
montycat
|
montycat-0.1.1/setup.cfg
ADDED
montycat-0.1.1/setup.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# setup.py
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
setup(
|
|
5
|
+
name='montycat',
|
|
6
|
+
version='0.1.1',
|
|
7
|
+
description='A Python client for MontyCat, NoSQL store utilizing Data Mesh architecture.',
|
|
8
|
+
packages=find_packages(),
|
|
9
|
+
zip_safe=False,
|
|
10
|
+
long_description=open('README.md').read(),
|
|
11
|
+
long_description_content_type='text/markdown',
|
|
12
|
+
author='MontyGovernance',
|
|
13
|
+
author_email='eugene.and.monty@gmail.com',
|
|
14
|
+
include_package_data=True,
|
|
15
|
+
install_requires=['orjson', 'xxhash', 'asyncio'],
|
|
16
|
+
classifiers=[
|
|
17
|
+
'Programming Language :: Python :: 3',
|
|
18
|
+
'License :: OSI Approved :: MIT License',
|
|
19
|
+
'Operating System :: OS Independent',
|
|
20
|
+
],
|
|
21
|
+
python_requires='>=3.9',
|
|
22
|
+
)
|