blissdata 0.3.4__py3-none-any.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.
- blissdata/__init__.py +17 -0
- blissdata/beacon/__init__.py +1 -0
- blissdata/beacon/_base.py +106 -0
- blissdata/beacon/config.py +24 -0
- blissdata/beacon/data.py +45 -0
- blissdata/beacon/files.py +141 -0
- blissdata/client.py +62 -0
- blissdata/common/__init__.py +15 -0
- blissdata/common/utils.py +159 -0
- blissdata/data/__init__.py +15 -0
- blissdata/data/events/__init__.py +22 -0
- blissdata/data/events/channel.py +149 -0
- blissdata/data/events/lima.py +478 -0
- blissdata/data/events/node.py +28 -0
- blissdata/data/events/scan.py +49 -0
- blissdata/data/events/walk.py +42 -0
- blissdata/data/expiration.py +42 -0
- blissdata/data/lima_image.py +465 -0
- blissdata/data/node.py +1619 -0
- blissdata/data/nodes/__init__.py +11 -0
- blissdata/data/nodes/channel.py +394 -0
- blissdata/data/nodes/dataset.py +82 -0
- blissdata/data/nodes/dataset_collection.py +12 -0
- blissdata/data/nodes/lima.py +422 -0
- blissdata/data/nodes/node_ref_channel.py +34 -0
- blissdata/data/nodes/proposal.py +12 -0
- blissdata/data/nodes/scan.py +192 -0
- blissdata/data/nodes/scan_group.py +12 -0
- blissdata/data/remote_node.py +204 -0
- blissdata/data/scan.py +666 -0
- blissdata/h5api/__init__.py +1 -0
- blissdata/h5api/abstract.py +97 -0
- blissdata/h5api/dynamic_hdf5.py +153 -0
- blissdata/h5api/file_arguments.py +28 -0
- blissdata/h5api/static_hdf5.py +139 -0
- blissdata/h5api/utils/__init__.py +0 -0
- blissdata/h5api/utils/bliss.py +138 -0
- blissdata/h5api/utils/hdf5.py +280 -0
- blissdata/h5api/utils/hdf5_retry.py +98 -0
- blissdata/h5api/utils/lima.py +286 -0
- blissdata/h5api/utils/types.py +13 -0
- blissdata/redis/__init__.py +12 -0
- blissdata/redis/caching.py +390 -0
- blissdata/redis/connection.py +169 -0
- blissdata/redis/manager.py +164 -0
- blissdata/redis/proxy.py +971 -0
- blissdata/redis/scripting.py +24 -0
- blissdata/settings.py +1174 -0
- blissdata/streaming.py +819 -0
- blissdata/streaming_events.py +355 -0
- blissdata/tests/__init__.py +0 -0
- blissdata/tests/beacon/__init__.py +0 -0
- blissdata/tests/beacon/test_config.py +26 -0
- blissdata/tests/beacon/test_data.py +50 -0
- blissdata/tests/beacon/test_files.py +80 -0
- blissdata/tests/conftest.py +0 -0
- blissdata/tests/h5api/__init__.py +0 -0
- blissdata/tests/h5api/scanner.py +390 -0
- blissdata/tests/h5api/test_dynamic_files.py +309 -0
- blissdata/tests/h5api/test_static_files.py +169 -0
- blissdata/tests/redis/__init__.py +0 -0
- blissdata/tests/redis/manager.py +23 -0
- blissdata-0.3.4.dist-info/LICENSE +165 -0
- blissdata-0.3.4.dist-info/METADATA +79 -0
- blissdata-0.3.4.dist-info/RECORD +67 -0
- blissdata-0.3.4.dist-info/WHEEL +5 -0
- blissdata-0.3.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Top-level API to Redis
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import socket
|
|
6
|
+
from typing import Dict, Optional, Set
|
|
7
|
+
import weakref
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
import gevent.lock
|
|
10
|
+
|
|
11
|
+
from blissdata.redis.connection import create_connection_pool
|
|
12
|
+
from blissdata.redis.connection import RedisDbConnectionPool
|
|
13
|
+
from blissdata.redis.proxy import RedisDbProxyBase
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class RedisPoolId:
|
|
18
|
+
db: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class RedisProxyId:
|
|
23
|
+
db: int
|
|
24
|
+
caching: bool
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class RedisAddress:
|
|
29
|
+
host: str
|
|
30
|
+
port: Optional[int]
|
|
31
|
+
sock: Optional[str]
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def factory(cls, address: Optional[str] = None):
|
|
35
|
+
host = None
|
|
36
|
+
port = None
|
|
37
|
+
sock = None
|
|
38
|
+
DEFAULT_REDIS_PORT = 6379
|
|
39
|
+
if address is None:
|
|
40
|
+
address = os.environ.get("REDIS_HOST")
|
|
41
|
+
if address is None:
|
|
42
|
+
host = "localhost"
|
|
43
|
+
port = DEFAULT_REDIS_PORT
|
|
44
|
+
elif ":" in address:
|
|
45
|
+
host, suffix = address.split(":", 1)
|
|
46
|
+
try:
|
|
47
|
+
port = int(suffix)
|
|
48
|
+
except ValueError:
|
|
49
|
+
sock = suffix
|
|
50
|
+
else:
|
|
51
|
+
host = address
|
|
52
|
+
port = DEFAULT_REDIS_PORT
|
|
53
|
+
return cls(host=host, port=port, sock=sock)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def url(self):
|
|
57
|
+
if self.sock is not None:
|
|
58
|
+
return f"unix://{self.sock}"
|
|
59
|
+
else:
|
|
60
|
+
return f"redis://{self.host}:{self.port}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RedisConnectionManager:
|
|
64
|
+
"""
|
|
65
|
+
Use `get_db_proxy` to create a connection or use an existing one.
|
|
66
|
+
Use `close_all_connections` to close all Redis connections.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
CLIENT_NAME = f"{socket.gethostname()}:{os.getpid()}"
|
|
70
|
+
|
|
71
|
+
def __init__(self, addresses: Dict[int, RedisAddress]):
|
|
72
|
+
self._addresses = addresses
|
|
73
|
+
|
|
74
|
+
self._get_proxy_lock = gevent.lock.Semaphore()
|
|
75
|
+
|
|
76
|
+
# Keep hard references to all shared Redis proxies
|
|
77
|
+
# (these proxies don't hold a `redis.Redis.Connection` instance)
|
|
78
|
+
self._shared_proxies: Dict[RedisProxyId, RedisDbProxyBase] = dict()
|
|
79
|
+
|
|
80
|
+
# Keep weak references to all shared Redis connection pools:
|
|
81
|
+
self._connection_pools: Dict[
|
|
82
|
+
RedisPoolId, RedisDbConnectionPool
|
|
83
|
+
] = weakref.WeakValueDictionary()
|
|
84
|
+
|
|
85
|
+
# Keep weak references to all cached Redis proxies which are not
|
|
86
|
+
# reused (although they could be but their cache with kep growing)
|
|
87
|
+
self._non_shared_proxies: Set[RedisDbProxyBase] = weakref.WeakSet()
|
|
88
|
+
|
|
89
|
+
# Hard references to the connection pools are held by the
|
|
90
|
+
# Redis proxies themselves. Connections of RedisDbConnectionPool
|
|
91
|
+
# are closed upon garbage collection of RedisDbConnectionPool. So
|
|
92
|
+
# when the proxies too a pool are the only ones having a hard
|
|
93
|
+
# reference too that pool, the connections are closed when all
|
|
94
|
+
# proxies are garbage collected.
|
|
95
|
+
|
|
96
|
+
def get_db_proxy(
|
|
97
|
+
self, db: int = 0, caching: bool = False, shared: bool = True
|
|
98
|
+
) -> RedisDbProxyBase:
|
|
99
|
+
"""Get a greenlet-safe proxy to a Redis database.
|
|
100
|
+
|
|
101
|
+
:param int db: Redis database too which we need a proxy
|
|
102
|
+
:param bool caching: client-side caching
|
|
103
|
+
:param bool shared: use a shared proxy held by the Beacon connection
|
|
104
|
+
"""
|
|
105
|
+
proxyid = RedisProxyId(db=db, caching=caching)
|
|
106
|
+
if shared:
|
|
107
|
+
return self._get_shared_db_proxy(proxyid)
|
|
108
|
+
else:
|
|
109
|
+
return self._get_non_shared_db_proxy(proxyid)
|
|
110
|
+
|
|
111
|
+
def close_all_connections(self):
|
|
112
|
+
# To close `redis.connection.Connection` you need to call its
|
|
113
|
+
# `disconnect` method (also called on garbage collection).
|
|
114
|
+
#
|
|
115
|
+
# Connection pools have a `disconnect` method that disconnect
|
|
116
|
+
# all their connections, which means close and destroy their
|
|
117
|
+
# socket instances.
|
|
118
|
+
#
|
|
119
|
+
# Note: closing a proxy will not close any connections
|
|
120
|
+
proxies = list(self._non_shared_proxies)
|
|
121
|
+
proxies.extend(self._shared_proxies.values())
|
|
122
|
+
self._shared_proxies = dict()
|
|
123
|
+
self._non_shared_proxies = weakref.WeakSet()
|
|
124
|
+
for proxy in proxies:
|
|
125
|
+
proxy.close()
|
|
126
|
+
proxy.connection_pool.disconnect()
|
|
127
|
+
|
|
128
|
+
def _get_shared_db_proxy(self, proxyid: RedisProxyId) -> RedisDbProxyBase:
|
|
129
|
+
"""Get a reusabed proxy and create it when it doesn't exist."""
|
|
130
|
+
with self._get_proxy_lock:
|
|
131
|
+
proxy = self._shared_proxies.get(proxyid)
|
|
132
|
+
if proxy is None:
|
|
133
|
+
pool = self._get_connection_pool(proxyid)
|
|
134
|
+
proxy = pool.create_proxy(caching=proxyid.caching)
|
|
135
|
+
self._shared_proxies[proxyid] = proxy
|
|
136
|
+
return proxy
|
|
137
|
+
|
|
138
|
+
def _get_non_shared_db_proxy(self, proxyid: RedisProxyId) -> RedisDbProxyBase:
|
|
139
|
+
"""Get a reusabed proxy and create it when it doesn't exist."""
|
|
140
|
+
with self._get_proxy_lock:
|
|
141
|
+
pool = self._get_connection_pool(proxyid)
|
|
142
|
+
proxy = pool.create_proxy(caching=proxyid.caching)
|
|
143
|
+
self._non_shared_proxies.add(proxy)
|
|
144
|
+
return proxy
|
|
145
|
+
|
|
146
|
+
def _get_connection_pool(self, proxyid: RedisProxyId) -> RedisDbConnectionPool:
|
|
147
|
+
"""Get a Redis connection pool (create when it does not exist yet)
|
|
148
|
+
for the db.
|
|
149
|
+
"""
|
|
150
|
+
poolid = RedisPoolId(db=proxyid.db)
|
|
151
|
+
pool = self._connection_pools.get(poolid)
|
|
152
|
+
if pool is None:
|
|
153
|
+
pool = create_connection_pool(
|
|
154
|
+
self._get_url(poolid.db), poolid.db, client_name=self.CLIENT_NAME
|
|
155
|
+
)
|
|
156
|
+
self._connection_pools[poolid] = pool
|
|
157
|
+
return pool
|
|
158
|
+
|
|
159
|
+
def _get_url(self, db: int) -> str:
|
|
160
|
+
"""Full Redis URL as a string"""
|
|
161
|
+
address = self._addresses.get(db)
|
|
162
|
+
if address is None:
|
|
163
|
+
raise RuntimeError(f"No Redis address specified for database {db}")
|
|
164
|
+
return address.url
|