hat-event 0.9.27__cp310.cp311.cp312.cp313-abi3-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.
Files changed (73) hide show
  1. hat/event/__init__.py +1 -0
  2. hat/event/adminer/__init__.py +18 -0
  3. hat/event/adminer/client.py +124 -0
  4. hat/event/adminer/common.py +27 -0
  5. hat/event/adminer/server.py +111 -0
  6. hat/event/backends/__init__.py +0 -0
  7. hat/event/backends/dummy.py +49 -0
  8. hat/event/backends/lmdb/__init__.py +9 -0
  9. hat/event/backends/lmdb/backend.py +319 -0
  10. hat/event/backends/lmdb/common.py +277 -0
  11. hat/event/backends/lmdb/conditions.py +102 -0
  12. hat/event/backends/lmdb/convert/__init__.py +0 -0
  13. hat/event/backends/lmdb/convert/__main__.py +8 -0
  14. hat/event/backends/lmdb/convert/convert_v06_to_v07.py +213 -0
  15. hat/event/backends/lmdb/convert/convert_v07_to_v09.py +175 -0
  16. hat/event/backends/lmdb/convert/main.py +88 -0
  17. hat/event/backends/lmdb/convert/v06.py +216 -0
  18. hat/event/backends/lmdb/convert/v07.py +508 -0
  19. hat/event/backends/lmdb/convert/v09.py +50 -0
  20. hat/event/backends/lmdb/convert/version.py +63 -0
  21. hat/event/backends/lmdb/environment.py +100 -0
  22. hat/event/backends/lmdb/latestdb.py +116 -0
  23. hat/event/backends/lmdb/manager/__init__.py +0 -0
  24. hat/event/backends/lmdb/manager/__main__.py +8 -0
  25. hat/event/backends/lmdb/manager/common.py +45 -0
  26. hat/event/backends/lmdb/manager/copy.py +92 -0
  27. hat/event/backends/lmdb/manager/main.py +34 -0
  28. hat/event/backends/lmdb/manager/query.py +215 -0
  29. hat/event/backends/lmdb/refdb.py +234 -0
  30. hat/event/backends/lmdb/systemdb.py +102 -0
  31. hat/event/backends/lmdb/timeseriesdb.py +486 -0
  32. hat/event/backends/memory.py +178 -0
  33. hat/event/common/__init__.py +144 -0
  34. hat/event/common/backend.py +91 -0
  35. hat/event/common/collection/__init__.py +8 -0
  36. hat/event/common/collection/common.py +28 -0
  37. hat/event/common/collection/list.py +19 -0
  38. hat/event/common/collection/tree.py +62 -0
  39. hat/event/common/common.py +176 -0
  40. hat/event/common/encoder.py +305 -0
  41. hat/event/common/json_schema_repo.json +1 -0
  42. hat/event/common/matches.py +44 -0
  43. hat/event/common/module.py +142 -0
  44. hat/event/common/sbs_repo.json +1 -0
  45. hat/event/common/subscription/__init__.py +22 -0
  46. hat/event/common/subscription/_csubscription.abi3.pyd +0 -0
  47. hat/event/common/subscription/common.py +145 -0
  48. hat/event/common/subscription/csubscription.py +47 -0
  49. hat/event/common/subscription/pysubscription.py +97 -0
  50. hat/event/component.py +284 -0
  51. hat/event/eventer/__init__.py +28 -0
  52. hat/event/eventer/client.py +260 -0
  53. hat/event/eventer/common.py +27 -0
  54. hat/event/eventer/server.py +286 -0
  55. hat/event/manager/__init__.py +0 -0
  56. hat/event/manager/__main__.py +8 -0
  57. hat/event/manager/common.py +48 -0
  58. hat/event/manager/main.py +387 -0
  59. hat/event/server/__init__.py +0 -0
  60. hat/event/server/__main__.py +8 -0
  61. hat/event/server/adminer_server.py +43 -0
  62. hat/event/server/engine.py +216 -0
  63. hat/event/server/engine_runner.py +127 -0
  64. hat/event/server/eventer_client.py +205 -0
  65. hat/event/server/eventer_client_runner.py +152 -0
  66. hat/event/server/eventer_server.py +119 -0
  67. hat/event/server/main.py +84 -0
  68. hat/event/server/main_runner.py +212 -0
  69. hat_event-0.9.27.dist-info/LICENSE +202 -0
  70. hat_event-0.9.27.dist-info/METADATA +108 -0
  71. hat_event-0.9.27.dist-info/RECORD +73 -0
  72. hat_event-0.9.27.dist-info/WHEEL +7 -0
  73. hat_event-0.9.27.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,127 @@
1
+ from collections.abc import Callable
2
+ import asyncio
3
+ import logging
4
+
5
+ from hat import aio
6
+ from hat import json
7
+
8
+ from hat.event import common
9
+ from hat.event.server.engine import create_engine
10
+ from hat.event.server.eventer_client import SyncedState
11
+ from hat.event.server.eventer_client_runner import EventerClientRunner
12
+ from hat.event.server.eventer_server import EventerServer
13
+
14
+
15
+ mlog: logging.Logger = logging.getLogger(__name__)
16
+ """Module logger"""
17
+
18
+
19
+ class EngineRunner(aio.Resource):
20
+
21
+ def __init__(self,
22
+ conf: json.Data,
23
+ backend: common.Backend,
24
+ eventer_server: EventerServer,
25
+ eventer_client_runner: EventerClientRunner | None,
26
+ reset_monitor_ready_cb: Callable[[], None]):
27
+ self._conf = conf
28
+ self._backend = backend
29
+ self._eventer_server = eventer_server
30
+ self._eventer_client_runner = eventer_client_runner
31
+ self._reset_monitor_ready_cb = reset_monitor_ready_cb
32
+ self._async_group = aio.Group()
33
+ self._engine = None
34
+ self._restart = asyncio.Event()
35
+
36
+ self.async_group.spawn(self._run)
37
+
38
+ @property
39
+ def async_group(self) -> aio.Group:
40
+ return self._async_group
41
+
42
+ async def set_synced(self,
43
+ server_id: common.ServerId,
44
+ state: SyncedState,
45
+ count: int | None):
46
+ if not self._engine or not self._engine.is_open:
47
+ return
48
+
49
+ data = {'state': state.name}
50
+ if state == SyncedState.SYNCED:
51
+ data['count'] = count
52
+
53
+ source = common.Source(type=common.SourceType.SERVER, id=0)
54
+ event = common.RegisterEvent(
55
+ type=('event', str(self._conf['server_id']), 'synced',
56
+ str(server_id)),
57
+ source_timestamp=None,
58
+ payload=common.EventPayloadJson(data))
59
+
60
+ await self._engine.register(source, [event])
61
+
62
+ async def _run(self):
63
+ try:
64
+ mlog.debug("staring engine runner loop")
65
+ while True:
66
+ await self._wait_while_remote_active()
67
+
68
+ self._restart.clear()
69
+
70
+ await self._eventer_server.set_status(common.Status.STARTING,
71
+ None)
72
+
73
+ mlog.debug("creating engine")
74
+ self._engine = await create_engine(
75
+ backend=self._backend,
76
+ eventer_server=self._eventer_server,
77
+ module_confs=self._conf['modules'],
78
+ server_id=self._conf['server_id'],
79
+ restart_cb=self._restart.set,
80
+ reset_monitor_ready_cb=self._reset_monitor_ready_cb)
81
+ await self._eventer_server.set_status(
82
+ common.Status.OPERATIONAL, self._engine)
83
+
84
+ async with self._async_group.create_subgroup() as subgroup:
85
+ await asyncio.wait(
86
+ [subgroup.spawn(self._engine.wait_closing),
87
+ subgroup.spawn(self._restart.wait)],
88
+ return_when=asyncio.FIRST_COMPLETED)
89
+
90
+ if not self._engine.is_open:
91
+ break
92
+
93
+ await self._close()
94
+
95
+ except Exception as e:
96
+ mlog.error("engine runner loop error: %s", e, exc_info=e)
97
+
98
+ finally:
99
+ mlog.debug("closing engine runner loop")
100
+ self.close()
101
+ await aio.uncancellable(self._close())
102
+
103
+ async def _close(self):
104
+ if self._engine:
105
+ self._engine.close()
106
+
107
+ await self._eventer_server.set_status(common.Status.STOPPING, None)
108
+
109
+ if self._engine:
110
+ await self._engine.async_close()
111
+
112
+ await self._backend.flush()
113
+
114
+ # TODO not needed with _wait_while_remote_active
115
+ # await self._eventer_server.notify_events([], True, True)
116
+
117
+ await self._eventer_server.set_status(common.Status.STANDBY, None)
118
+
119
+ async def _wait_while_remote_active(self):
120
+ if not self._eventer_client_runner:
121
+ return
122
+
123
+ while self._eventer_client_runner.remote_active:
124
+ event = asyncio.Event()
125
+ with self._eventer_client_runner.register_remote_active_cb(
126
+ lambda _: event.set()):
127
+ await event.wait()
@@ -0,0 +1,205 @@
1
+ import collections
2
+ import contextlib
3
+ import enum
4
+ import logging
5
+ import typing
6
+
7
+ from hat import aio
8
+ from hat.drivers import tcp
9
+
10
+ from hat.event import common
11
+ from hat.event import eventer
12
+
13
+
14
+ mlog: logging.Logger = logging.getLogger(__name__)
15
+ """Module logger"""
16
+
17
+
18
+ class SyncedState(enum.Enum):
19
+ """Synced state"""
20
+ CONNECTED = 0
21
+ SYNCING = 1
22
+ SYNCED = 2
23
+
24
+
25
+ StatusCb: typing.TypeAlias = aio.AsyncCallable[[common.Status], None]
26
+ """Status callback"""
27
+
28
+ SyncedCb: typing.TypeAlias = aio.AsyncCallable[[SyncedState, int | None], None]
29
+ """Synced callback"""
30
+
31
+
32
+ async def create_eventer_client(addr: tcp.Address,
33
+ client_name: str,
34
+ local_server_id: common.ServerId,
35
+ remote_server_id: common.ServerId,
36
+ backend: common.Backend,
37
+ *,
38
+ client_token: str | None = None,
39
+ status_cb: StatusCb | None = None,
40
+ synced_cb: SyncedCb | None = None,
41
+ **kwargs
42
+ ) -> 'EventerClient':
43
+ """Create eventer client"""
44
+ client = EventerClient()
45
+ client._local_server_id = local_server_id
46
+ client._remote_server_id = remote_server_id
47
+ client._backend = backend
48
+ client._status_cb = status_cb
49
+ client._synced_cb = synced_cb
50
+ client._synced = None
51
+ client._events_queue = collections.deque()
52
+
53
+ client._client = await eventer.connect(addr=addr,
54
+ client_name=client_name,
55
+ client_token=client_token,
56
+ subscriptions=[('*', )],
57
+ server_id=remote_server_id,
58
+ persisted=True,
59
+ status_cb=client._on_status,
60
+ events_cb=client._on_events,
61
+ **kwargs)
62
+
63
+ try:
64
+ client.async_group.spawn(client._synchronize)
65
+
66
+ except BaseException:
67
+ await aio.uncancellable(client.async_close())
68
+ raise
69
+
70
+ return client
71
+
72
+
73
+ class EventerClient(aio.Resource):
74
+ """Eventer client
75
+
76
+ For creating new client see `create_eventer_client` coroutine.
77
+
78
+ """
79
+
80
+ @property
81
+ def async_group(self) -> aio.Group:
82
+ """Async group"""
83
+ return self._client.async_group
84
+
85
+ @property
86
+ def status(self) -> common.Status:
87
+ """Status"""
88
+ return self._client.status
89
+
90
+ @property
91
+ def synced(self) -> SyncedState | None:
92
+ """Synced state"""
93
+ return self._synced
94
+
95
+ async def _on_status(self, client, status):
96
+ if status == common.Status.OPERATIONAL and self._synced:
97
+ data = {'state': self._synced.name}
98
+ if self._synced == SyncedState.SYNCED:
99
+ data['count'] = None
100
+
101
+ with contextlib.suppress(Exception):
102
+ await self._client.register([
103
+ common.RegisterEvent(
104
+ type=('event', str(self._local_server_id), 'synced',
105
+ str(self._remote_server_id)),
106
+ source_timestamp=None,
107
+ payload=common.EventPayloadJson(data))])
108
+
109
+ if not self._status_cb:
110
+ return
111
+
112
+ await aio.call(self._status_cb, status)
113
+
114
+ async def _on_events(self, client, events):
115
+ mlog.debug("received %s notify events", len(events))
116
+
117
+ if self._events_queue is not None:
118
+ self._events_queue.append(events)
119
+ return
120
+
121
+ await self._backend.register(events)
122
+
123
+ async def _synchronize(self):
124
+ mlog.debug("starting synchronization")
125
+
126
+ try:
127
+ last_event_id = await self._backend.get_last_event_id(
128
+ self._remote_server_id)
129
+ events = collections.deque()
130
+ result = common.QueryResult([], True)
131
+ synced_counter = 0
132
+
133
+ await self._set_synced(SyncedState.CONNECTED, None)
134
+
135
+ while result.more_follows:
136
+ params = common.QueryServerParams(
137
+ server_id=self._remote_server_id,
138
+ persisted=True,
139
+ last_event_id=last_event_id)
140
+ result = await self._client.query(params)
141
+
142
+ mlog.debug("received %s query events", len(result.events))
143
+ events.extend(result.events)
144
+
145
+ if result.events and synced_counter == 0:
146
+ await self._set_synced(SyncedState.SYNCING, None)
147
+
148
+ synced_counter += len(result.events)
149
+ if not events:
150
+ continue
151
+
152
+ last_event_id = events[-1].id
153
+
154
+ while events[0].id.session != events[-1].id.session:
155
+ session_id = events[0].id.session
156
+ session_events = collections.deque()
157
+
158
+ while events[0].id.session == session_id:
159
+ session_events.append(events.popleft())
160
+
161
+ await self._backend.register(session_events)
162
+
163
+ if events:
164
+ await self._backend.register(events)
165
+
166
+ mlog.debug("processing cached notify events")
167
+ while self._events_queue:
168
+ events = [event for event in self._events_queue.popleft()
169
+ if event.id > last_event_id]
170
+ if not events:
171
+ continue
172
+
173
+ await self._backend.register(events)
174
+
175
+ self._events_queue = None
176
+
177
+ mlog.debug("synchronized %s events", synced_counter)
178
+ await self._set_synced(SyncedState.SYNCED, synced_counter)
179
+
180
+ except ConnectionError:
181
+ mlog.debug("connection closed")
182
+ self.close()
183
+
184
+ except Exception as e:
185
+ mlog.error("synchronization error: %s", e, exc_info=e)
186
+ self.close()
187
+
188
+ async def _set_synced(self, state, count):
189
+ self._synced = state
190
+
191
+ data = {'state': state.name}
192
+ if state == SyncedState.SYNCED:
193
+ data['count'] = count
194
+
195
+ await self._client.register([
196
+ common.RegisterEvent(
197
+ type=('event', str(self._local_server_id), 'synced',
198
+ str(self._remote_server_id)),
199
+ source_timestamp=None,
200
+ payload=common.EventPayloadJson(data))])
201
+
202
+ if not self._synced_cb:
203
+ return
204
+
205
+ await aio.call(self._synced_cb, state, count)
@@ -0,0 +1,152 @@
1
+ from collections.abc import Callable
2
+ import asyncio
3
+ import functools
4
+ import logging
5
+ import types
6
+ import typing
7
+
8
+ from hat import aio
9
+ from hat import json
10
+ from hat import util
11
+ from hat.drivers import tcp
12
+ import hat.monitor.component
13
+
14
+ from hat.event import common
15
+ from hat.event.server.eventer_client import (SyncedState,
16
+ create_eventer_client)
17
+
18
+
19
+ mlog: logging.Logger = logging.getLogger(__name__)
20
+ """Module logger"""
21
+
22
+
23
+ SyncedCb: typing.TypeAlias = aio.AsyncCallable[
24
+ [common.ServerId, SyncedState, int | None],
25
+ None]
26
+
27
+
28
+ class EventerServerData(typing.NamedTuple):
29
+ server_id: common.ServerId
30
+ addr: tcp.Address
31
+
32
+
33
+ class EventerClientRunner(aio.Resource):
34
+
35
+ def __init__(self,
36
+ conf: json.Data,
37
+ backend: common.Backend,
38
+ synced_cb: SyncedCb,
39
+ reconnect_delay: float = 5):
40
+ self._conf = conf
41
+ self._backend = backend
42
+ self._synced_cb = synced_cb
43
+ self._reconnect_delay = reconnect_delay
44
+ self._async_group = aio.Group()
45
+ self._remote_active = False
46
+ self._remote_active_cbs = util.CallbackRegistry()
47
+ self._valid_server_data = set()
48
+ self._connecting_server_data = set()
49
+ self._remote_active_server_data = set()
50
+
51
+ @property
52
+ def async_group(self) -> aio.Group:
53
+ return self._async_group
54
+
55
+ @property
56
+ def remote_active(self) -> bool:
57
+ return self._remote_active
58
+
59
+ def register_remote_active_cb(self,
60
+ cb: Callable[[bool], None]
61
+ ) -> util.RegisterCallbackHandle:
62
+ return self._remote_active_cbs.register(cb)
63
+
64
+ def set_monitor_state(self, state: hat.monitor.component.State):
65
+ self._valid_server_data = set(_get_eventer_server_data(
66
+ group=self._conf['monitor_component']['group'],
67
+ server_token=self._conf.get('server_token'),
68
+ state=state))
69
+
70
+ for server_data in self._valid_server_data:
71
+ if server_data in self._connecting_server_data:
72
+ continue
73
+
74
+ self.async_group.spawn(self._client_loop, server_data)
75
+ self._connecting_server_data.add(server_data)
76
+
77
+ async def _client_loop(self, server_data):
78
+ try:
79
+ mlog.debug("staring eventer client runner loop")
80
+ while server_data in self._valid_server_data:
81
+ self._set_client_status(server_data, None)
82
+
83
+ try:
84
+ mlog.debug("creating eventer client")
85
+ eventer_client = await create_eventer_client(
86
+ addr=server_data.addr,
87
+ client_name=f"event/{self._conf['name']}",
88
+ local_server_id=self._conf['server_id'],
89
+ remote_server_id=server_data.server_id,
90
+ backend=self._backend,
91
+ client_token=self._conf.get('server_token'),
92
+ status_cb=functools.partial(self._set_client_status,
93
+ server_data),
94
+ synced_cb=functools.partial(self._synced_cb,
95
+ server_data.server_id))
96
+
97
+ except Exception:
98
+ await asyncio.sleep(self._reconnect_delay)
99
+ continue
100
+
101
+ self._set_client_status(server_data, eventer_client.status)
102
+
103
+ try:
104
+ await eventer_client.wait_closing()
105
+
106
+ finally:
107
+ await aio.uncancellable(eventer_client.async_close())
108
+
109
+ except Exception as e:
110
+ mlog.error("eventer client runner loop error: %s", e, exc_info=e)
111
+ self.close()
112
+
113
+ finally:
114
+ mlog.debug("closing eventer client runner loop")
115
+ self._connecting_server_data.remove(server_data)
116
+ self._set_client_status(server_data, None)
117
+
118
+ def _set_client_status(self, server_data, status):
119
+ if status is None or status == common.Status.STANDBY:
120
+ self._remote_active_server_data.discard(server_data)
121
+
122
+ else:
123
+ self._remote_active_server_data.add(server_data)
124
+
125
+ remote_active = bool(self._remote_active_server_data)
126
+ if remote_active == self._remote_active:
127
+ return
128
+
129
+ self._remote_active = remote_active
130
+ self._remote_active_cbs.notify(remote_active)
131
+
132
+
133
+ def _get_eventer_server_data(group, server_token, state):
134
+ for info in state.components:
135
+ if info == state.info or info.group != group:
136
+ continue
137
+
138
+ server_id = json.get(info.data, 'server_id')
139
+ host = json.get(info.data, ['eventer_server', 'host'])
140
+ port = json.get(info.data, ['eventer_server', 'port'])
141
+ token = json.get(info.data, 'server_token')
142
+ if (not isinstance(server_id, int) or
143
+ not isinstance(host, str) or
144
+ not isinstance(port, int) or
145
+ not isinstance(token, (str, types.NoneType))):
146
+ continue
147
+
148
+ if server_token is not None and token != server_token:
149
+ continue
150
+
151
+ yield EventerServerData(server_id=server_id,
152
+ addr=tcp.Address(host, port))
@@ -0,0 +1,119 @@
1
+ """Eventer server"""
2
+
3
+ from collections.abc import Collection, Iterable
4
+ import logging
5
+
6
+ from hat import aio
7
+ from hat.drivers import tcp
8
+
9
+ from hat.event import common
10
+ from hat.event import eventer
11
+
12
+
13
+ mlog: logging.Logger = logging.getLogger(__name__)
14
+ """Module logger"""
15
+
16
+
17
+ async def create_eventer_server(addr: tcp.Address,
18
+ backend: common.Backend,
19
+ server_id: int,
20
+ *,
21
+ server_token: str | None = None,
22
+ **kwargs
23
+ ) -> 'EventerServer':
24
+ """Create eventer server"""
25
+ server = EventerServer()
26
+ server._backend = backend
27
+ server._server_id = server_id
28
+ server._server_token = server_token
29
+ server._engine = None
30
+
31
+ server._srv = await eventer.listen(addr,
32
+ connected_cb=server._on_connected,
33
+ disconnected_cb=server._on_disconnected,
34
+ register_cb=server._on_register,
35
+ query_cb=server._on_query,
36
+ **kwargs)
37
+
38
+ return server
39
+
40
+
41
+ class EventerServer(aio.Resource):
42
+ """Eventer server
43
+
44
+ For creating new server see `create_eventer_server` coroutine.
45
+
46
+ """
47
+
48
+ @property
49
+ def async_group(self) -> aio.Group:
50
+ """Async group"""
51
+ return self._srv.async_group
52
+
53
+ def get_client_names(self) -> Iterable[tuple[common.Source, str]]:
54
+ """Get client names"""
55
+ for info in self._srv.get_conn_infos():
56
+ yield _get_source(info.id), info.client_name
57
+
58
+ async def set_status(self,
59
+ status: common.Status,
60
+ engine: common.Engine | None):
61
+ """Set status"""
62
+ if status == common.Status.OPERATIONAL:
63
+ if not engine:
64
+ raise ValueError('invalid status/engine')
65
+
66
+ else:
67
+ if engine:
68
+ raise ValueError('invalid status/engine')
69
+
70
+ self._engine = engine
71
+ await self._srv.set_status(status)
72
+
73
+ async def notify_events(self,
74
+ events: Collection[common.Event],
75
+ persisted: bool,
76
+ with_ack: bool = False):
77
+ """Notify events"""
78
+ await self._srv.notify_events(events, persisted, with_ack)
79
+
80
+ async def _on_connected(self, info):
81
+ if (info.client_token is not None and
82
+ info.client_token != self._server_token):
83
+ raise Exception('invalid client token')
84
+
85
+ if not self._engine or not self._engine.is_open:
86
+ return
87
+
88
+ source = _get_source(info.id)
89
+ register_event = self._create_eventer_event(info, 'CONNECTED')
90
+ await self._engine.register(source, [register_event])
91
+
92
+ async def _on_disconnected(self, info):
93
+ if not self._engine or not self._engine.is_open:
94
+ return
95
+
96
+ source = _get_source(info.id)
97
+ register_event = self._create_eventer_event(info, 'DISCONNECTED')
98
+ await self._engine.register(source, [register_event])
99
+
100
+ async def _on_register(self, info, register_events):
101
+ if not self._engine:
102
+ return
103
+
104
+ source = _get_source(info.id)
105
+ return await self._engine.register(source, register_events)
106
+
107
+ async def _on_query(self, info, params):
108
+ return await self._backend.query(params)
109
+
110
+ def _create_eventer_event(self, info, status):
111
+ return common.RegisterEvent(
112
+ type=('event', str(self._server_id), 'eventer', info.client_name),
113
+ source_timestamp=None,
114
+ payload=common.EventPayloadJson(status))
115
+
116
+
117
+ def _get_source(source_id):
118
+ return common.Source(type=common.SourceType.EVENTER,
119
+ id=source_id)
@@ -0,0 +1,84 @@
1
+ """Event server main"""
2
+
3
+ from pathlib import Path
4
+ import argparse
5
+ import asyncio
6
+ import contextlib
7
+ import logging.config
8
+ import sys
9
+
10
+ import appdirs
11
+
12
+ from hat import aio
13
+ from hat import json
14
+
15
+ from hat.event import common
16
+ from hat.event.server.main_runner import MainRunner
17
+
18
+
19
+ mlog: logging.Logger = logging.getLogger('hat.event.server.main')
20
+ """Module logger"""
21
+
22
+ user_conf_dir: Path = Path(appdirs.user_config_dir('hat'))
23
+ """User configuration directory path"""
24
+
25
+
26
+ def create_argument_parser() -> argparse.ArgumentParser:
27
+ """Create argument parser"""
28
+ parser = argparse.ArgumentParser()
29
+ parser.add_argument(
30
+ '--conf', metavar='PATH', type=Path, default=None,
31
+ help="configuration defined by hat-event://server.yaml "
32
+ "(default $XDG_CONFIG_HOME/hat/event.{yaml|yml|toml|json})")
33
+ return parser
34
+
35
+
36
+ def main():
37
+ """Event Server"""
38
+ parser = create_argument_parser()
39
+ args = parser.parse_args()
40
+ conf = json.read_conf(args.conf, user_conf_dir / 'event')
41
+ sync_main(conf)
42
+
43
+
44
+ def sync_main(conf: json.Data):
45
+ """Sync main entry point"""
46
+ aio.init_asyncio()
47
+
48
+ common.json_schema_repo.validate('hat-event://server.yaml', conf)
49
+
50
+ info = common.import_backend_info(conf['backend']['module'])
51
+ if info.json_schema_repo and info.json_schema_id:
52
+ info.json_schema_repo.validate(info.json_schema_id, conf['backend'])
53
+
54
+ for module_conf in conf['modules']:
55
+ info = common.import_module_info(module_conf['module'])
56
+ if info.json_schema_repo and info.json_schema_id:
57
+ info.json_schema_repo.validate(info.json_schema_id, module_conf)
58
+
59
+ log_conf = conf.get('log')
60
+ if log_conf:
61
+ logging.config.dictConfig(log_conf)
62
+
63
+ with contextlib.suppress(asyncio.CancelledError):
64
+ aio.run_asyncio(async_main(conf))
65
+
66
+
67
+ async def async_main(conf: json.Data):
68
+ """Async main entry point"""
69
+ main_runner = MainRunner(conf)
70
+
71
+ async def cleanup():
72
+ await main_runner.async_close()
73
+ await asyncio.sleep(0.1)
74
+
75
+ try:
76
+ await main_runner.wait_closing()
77
+
78
+ finally:
79
+ await aio.uncancellable(cleanup())
80
+
81
+
82
+ if __name__ == '__main__':
83
+ sys.argv[0] = 'hat-event-server'
84
+ sys.exit(main())