moat-kv 0.70.24__py3-none-any.whl → 0.71.6__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.
Files changed (127) hide show
  1. moat/kv/__init__.py +6 -7
  2. moat/kv/_cfg.yaml +5 -8
  3. moat/kv/actor/__init__.py +2 -1
  4. moat/kv/actor/deletor.py +4 -1
  5. moat/kv/auth/__init__.py +12 -13
  6. moat/kv/auth/_test.py +4 -1
  7. moat/kv/auth/password.py +11 -7
  8. moat/kv/backend/mqtt.py +4 -8
  9. moat/kv/client.py +20 -39
  10. moat/kv/code.py +3 -3
  11. moat/kv/command/data.py +4 -3
  12. moat/kv/command/dump/__init__.py +29 -29
  13. moat/kv/command/internal.py +2 -3
  14. moat/kv/command/job.py +1 -2
  15. moat/kv/command/type.py +3 -6
  16. moat/kv/data.py +9 -8
  17. moat/kv/errors.py +16 -8
  18. moat/kv/mock/__init__.py +2 -12
  19. moat/kv/model.py +28 -32
  20. moat/kv/obj/__init__.py +3 -3
  21. moat/kv/obj/command.py +3 -3
  22. moat/kv/runner.py +4 -5
  23. moat/kv/server.py +106 -126
  24. moat/kv/types.py +8 -6
  25. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
  26. moat_kv-0.71.6.dist-info/RECORD +47 -0
  27. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
  28. moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
  29. moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
  30. moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
  31. moat_kv-0.71.6.dist-info/top_level.txt +1 -0
  32. build/lib/docs/source/conf.py +0 -201
  33. build/lib/examples/pathify.py +0 -45
  34. build/lib/moat/kv/__init__.py +0 -19
  35. build/lib/moat/kv/_cfg.yaml +0 -97
  36. build/lib/moat/kv/_main.py +0 -91
  37. build/lib/moat/kv/actor/__init__.py +0 -98
  38. build/lib/moat/kv/actor/deletor.py +0 -139
  39. build/lib/moat/kv/auth/__init__.py +0 -444
  40. build/lib/moat/kv/auth/_test.py +0 -166
  41. build/lib/moat/kv/auth/password.py +0 -234
  42. build/lib/moat/kv/auth/root.py +0 -58
  43. build/lib/moat/kv/backend/__init__.py +0 -67
  44. build/lib/moat/kv/backend/mqtt.py +0 -74
  45. build/lib/moat/kv/backend/serf.py +0 -45
  46. build/lib/moat/kv/client.py +0 -1025
  47. build/lib/moat/kv/code.py +0 -236
  48. build/lib/moat/kv/codec.py +0 -11
  49. build/lib/moat/kv/command/__init__.py +0 -1
  50. build/lib/moat/kv/command/acl.py +0 -180
  51. build/lib/moat/kv/command/auth.py +0 -261
  52. build/lib/moat/kv/command/code.py +0 -293
  53. build/lib/moat/kv/command/codec.py +0 -186
  54. build/lib/moat/kv/command/data.py +0 -265
  55. build/lib/moat/kv/command/dump/__init__.py +0 -143
  56. build/lib/moat/kv/command/error.py +0 -149
  57. build/lib/moat/kv/command/internal.py +0 -248
  58. build/lib/moat/kv/command/job.py +0 -433
  59. build/lib/moat/kv/command/log.py +0 -53
  60. build/lib/moat/kv/command/server.py +0 -114
  61. build/lib/moat/kv/command/type.py +0 -201
  62. build/lib/moat/kv/config.py +0 -46
  63. build/lib/moat/kv/data.py +0 -216
  64. build/lib/moat/kv/errors.py +0 -561
  65. build/lib/moat/kv/exceptions.py +0 -126
  66. build/lib/moat/kv/mock/__init__.py +0 -101
  67. build/lib/moat/kv/mock/mqtt.py +0 -159
  68. build/lib/moat/kv/mock/serf.py +0 -250
  69. build/lib/moat/kv/mock/tracer.py +0 -63
  70. build/lib/moat/kv/model.py +0 -1069
  71. build/lib/moat/kv/obj/__init__.py +0 -646
  72. build/lib/moat/kv/obj/command.py +0 -241
  73. build/lib/moat/kv/runner.py +0 -1347
  74. build/lib/moat/kv/server.py +0 -2809
  75. build/lib/moat/kv/types.py +0 -513
  76. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
  77. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
  78. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
  79. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
  80. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
  81. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
  89. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
  90. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
  91. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
  92. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
  93. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
  94. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
  95. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
  96. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
  97. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
  98. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
  99. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
  100. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
  101. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
  102. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
  103. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
  104. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
  105. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
  106. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
  107. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
  108. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
  109. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
  110. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
  111. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
  112. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
  113. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
  114. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
  115. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
  116. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
  117. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
  118. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
  119. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
  120. docs/source/conf.py +0 -201
  121. examples/pathify.py +0 -45
  122. moat/kv/backend/serf.py +0 -45
  123. moat/kv/codec.py +0 -11
  124. moat/kv/mock/serf.py +0 -250
  125. moat_kv-0.70.24.dist-info/RECORD +0 -137
  126. moat_kv-0.70.24.dist-info/top_level.txt +0 -9
  127. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,45 +0,0 @@
1
- #!/usr/bin/python3
2
-
3
- # Batch-convert data. In this case I had some entries which were stored as
4
- # a list, but using Path made much more sense (esp when you need to
5
- # view/edit the yaml export).
6
-
7
- import anyio
8
- from moat.kv.client import open_client
9
- from moat.util import P, yload, Path
10
- import asyncclick as click
11
-
12
-
13
- def conv(m, s: str) -> bool:
14
- try:
15
- d = m.value[s]
16
- except KeyError:
17
- return 0
18
- if isinstance(d, Path):
19
- return 0
20
- if not isinstance(d, Sequence):
21
- return 0
22
- d = Path.build(d)
23
- m.value[s] = d
24
- return 1
25
-
26
-
27
- @click.command()
28
- @click.argument("path", type=P)
29
- @click.argument("keys", type=str, nargs=-1)
30
- async def main(path, keys):
31
- if not keys:
32
- keys = "src dest dst state".split()
33
- with open("/etc/moat.kv.cfg") as cff:
34
- cfg = yload(cff)
35
- async with open_client(**cfg) as client:
36
- async for m in client.get_tree(path, nchain=2):
37
- n = 0
38
- for k in keys:
39
- n += conv(m, k)
40
- if n:
41
- await client.set(ORIG + m.path, value=m.value, chain=m.chain)
42
-
43
-
44
- if __name__ == "__main__":
45
- main()
@@ -1,19 +0,0 @@
1
- # pylint: disable=W0703,C0103
2
- from __future__ import annotations
3
-
4
- __path__ = __import__("pkgutil").extend_path(__path__, __name__)
5
-
6
- try:
7
- import warning
8
- import pkg_resources # part of setuptools
9
-
10
- with warnings.filterwarnings("ignore"):
11
- _version = pkg_resources.require("moat.kv")[0].version
12
- del pkg_resources
13
- del warnings
14
-
15
- _version_tuple = tuple(int(x) for x in _version.split("."))
16
-
17
- except Exception: # pragma: no cover
18
- _version = "0.0.1"
19
- _version_tuple = (0, 0, 1)
@@ -1,97 +0,0 @@
1
- conn:
2
- # client: controls how to talk to the MoaT-KV server
3
- host: localhost
4
- port: 27586
5
- ssl: false
6
- # ssl:
7
- # cert: '/path/to/cert.pem',key='/path/to/cert.key'
8
- init_timeout: 5
9
- # time to wait for connection plus greeting
10
- auth: null
11
- # no auth used by default
12
- name: null
13
- # defaults to a seqnum
14
- config:
15
- prefix: !P :.moat.kv.config
16
- errors:
17
- prefix: !P :.moat.kv.error
18
- codes:
19
- prefix: !P :.moat.kv.code.proc
20
- modules:
21
- prefix: !P :.moat.kv.code.module
22
- runner: # for moat.kv.runner.RunnerRoot
23
- # storage for runnable commands
24
- prefix: !P :.moat.kv.run"
25
- # storage for runner states
26
- state: !P :.moat.kv.state"
27
-
28
- name: "run"
29
- # Serf event name, suffixed by subpath
30
-
31
- start_delay: 1
32
- # time to wait between job starts. Not optional.
33
-
34
- ping: -15
35
- # set an I-am-running message every those-many seconds
36
- # positive: set in moat.kv, negative: broadcast to :moat.kv.run tag
37
-
38
- actor:
39
- # Actor config, required for Runner
40
- cycle: 20
41
- nodes: -1
42
- splits: 5
43
- n_hosts: 3
44
- version: 1
45
- sub:
46
- # tags for various runner modes
47
- group: "any"
48
- single: "at"
49
- all: "all"
50
- server:
51
- # server-side configuration
52
- buffer: 10
53
- # per-stream buffer
54
-
55
- backend: "mqtt"
56
- # default
57
- mqtt:
58
- uri: "mqtt://localhost:1883"
59
- serf:
60
- host: "localhost"
61
- port: 7373
62
-
63
- # event message path/topic prefix
64
- root: !P moat.kv
65
-
66
- paranoia: False
67
- # typecheck server-to-server updates?
68
- #
69
- # which addresses/ports to accept MoaT-KV connections on
70
- bind: [{}]
71
- bind_default:
72
- # default values for all elements of "bind"
73
- host: "localhost"
74
- port: PORT
75
- ssl: False
76
- change:
77
- length: 5
78
- # chain length: use max nr of network sections +1
79
- ping:
80
- cycle: 10
81
- gap: 2
82
- # asyncserf.Actor config timing for server sync
83
- # ping also controls minimum server startup time
84
- delete:
85
- # asyncserf.Actor config timing for deletion
86
- cycle: 100
87
- gap: 10
88
- version: 1
89
- paranoia: false
90
- # typecheck server>server updates?
91
-
92
- # how does a new server reach existing nodes, to download state?
93
- domain: null
94
- # domain in which to look up node names, if not in hostmap
95
- hostmap: # map MoaT-KV server names to connect destinations
96
- test1: ["localhost", 27586]
97
- test2: ["does-not-exist.invalid", 27586]
@@ -1,91 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Basic DistKV support
4
-
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import logging
10
-
11
- import asyncclick as click
12
- from moat.util import attrdict, combine_dict, load_subgroup, CFG, ensure_cfg
13
-
14
- from moat.kv.auth import gen_auth
15
- from moat.kv.client import client_scope
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- ensure_cfg("moat.kv")
21
-
22
-
23
- class NullObj:
24
- """
25
- This helper defers raising an exception until one of its attributes is
26
- actually accessed.
27
- """
28
-
29
- def __init__(self, exc):
30
- self._exc = exc
31
-
32
- def __call__(self, *a, **kw):
33
- raise self._exc
34
-
35
- def __await__(self):
36
- raise self._exc
37
-
38
- def __getattr__(self, k):
39
- if k[0] == "_" and k not in ("_request", "_cfg"):
40
- return object.__getattribute__(self, k)
41
- raise self._exc
42
-
43
-
44
- @load_subgroup(sub_pre="moat.kv.command", sub_post="cli", ext_pre="moat.kv", ext_post="_main.cli")
45
- @click.option("-h", "--host", default=None, help=f"Host to use. Default: {CFG.kv.conn.host}")
46
- @click.option(
47
- "-p",
48
- "--port",
49
- type=int,
50
- default=None,
51
- help=f"Port to use. Default: {CFG.kv.conn.port}",
52
- )
53
- @click.option(
54
- "-a",
55
- "--auth",
56
- type=str,
57
- default=None,
58
- help="Auth params. =file or 'type param=value…' Default: _anon",
59
- )
60
- @click.option("-m", "--metadata", is_flag=True, help="Include/print metadata.")
61
- @click.pass_context
62
- async def cli(ctx, host, port, auth, metadata):
63
- """The MoaT Key-Value subsystem.
64
-
65
- All commands (except 'server' and 'dump') connect to a MoaT-KV server.
66
- """
67
- obj = ctx.obj
68
- cfg = attrdict()
69
- if host is not None:
70
- cfg.host = host
71
- if port is not None:
72
- cfg.port = port
73
-
74
- if auth is not None:
75
- cfg.auth = gen_auth(auth)
76
- if obj.DEBUG:
77
- cfg.auth._DEBUG = True
78
-
79
- cfg = combine_dict(attrdict(kv=attrdict(conn=cfg)), obj.cfg, cls=attrdict)
80
-
81
- obj.meta = 3 if metadata else False
82
-
83
- try:
84
- if ctx.invoked_subcommand in {None, "server", "dump"}:
85
- obj.client = NullObj(RuntimeError("Not a client command"))
86
- else:
87
- obj.client = await client_scope(**cfg.kv)
88
- except OSError as exc:
89
- obj.client = NullObj(exc)
90
- else:
91
- logger.debug("Connected.")
@@ -1,98 +0,0 @@
1
- """
2
- This module implements a :class:`asyncactor.Actor` which works on top of
3
- a MoaT-KV client.
4
- """
5
- from __future__ import annotations
6
-
7
- from asyncactor import Actor
8
- from asyncactor.abc import MonitorStream, Transport
9
-
10
- __all__ = [
11
- "ClientActor",
12
- "ActorState",
13
- "BrokenState",
14
- "DetachedState",
15
- "PartialState",
16
- "CompleteState",
17
- ]
18
-
19
-
20
- class ClientActor(Actor):
21
- def __init__(self, client, *a, topic, **kw):
22
- super().__init__(ClientTransport(client, topic), *a, **kw)
23
-
24
-
25
- class ClientTransport(Transport):
26
- """
27
- This class exports the client's direct messaging interface to the
28
- actor.
29
- """
30
-
31
- def __init__(self, client, topic):
32
- self.client = client
33
- self.topic = topic
34
-
35
- def monitor(self):
36
- return ClientMonitor(self)
37
-
38
- async def send(self, payload):
39
- await self.client.msg_send(self.topic, payload)
40
-
41
-
42
- class ClientMonitor(MonitorStream):
43
- _mon1 = None
44
- _mon2 = None
45
- _it = None
46
-
47
- async def __aenter__(self):
48
- self._mon1 = self.transport.client.msg_monitor(self.transport.topic)
49
- self._mon2 = await self._mon1.__aenter__()
50
- return self
51
-
52
- async def __aexit__(self, *tb):
53
- return await self._mon1.__aexit__(*tb)
54
-
55
- def __aiter__(self):
56
- self._it = self._mon2.__aiter__()
57
- return self
58
-
59
- async def __anext__(self):
60
- msg = await self._it.__anext__()
61
- return msg.data
62
-
63
-
64
- # The following events are used by Runner etc. to notify running jobs
65
- # about the current connectivity state.
66
- #
67
- class ActorState:
68
- """base class for states"""
69
-
70
- def __init__(self, msg=None):
71
- self.msg = msg
72
-
73
- def __repr__(self):
74
- return "<%s:%r>" % (self.__class__.__name__, self.msg)
75
-
76
-
77
- class BrokenState(ActorState):
78
- """I have no idea what's happening, probably nothing good"""
79
-
80
- pass
81
-
82
-
83
- class DetachedState(ActorState):
84
- """I am detached, my actor group is not visible"""
85
-
86
- pass
87
-
88
-
89
- class PartialState(ActorState):
90
- """Some but not all members of my actor group are visible"""
91
-
92
- pass
93
-
94
-
95
- class CompleteState(ActorState):
96
- """All members of my actor group are visible"""
97
-
98
- pass
@@ -1,139 +0,0 @@
1
- """
2
- This module implements additional code for the server-side DeleteActor,
3
- which is used to clean up the list of deleted nodes.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- import weakref
9
- from collections import deque
10
-
11
- import anyio
12
- from asyncactor import Actor, PingEvent, TagEvent
13
- from asyncactor.backend import get_transport
14
-
15
- TAGS = 4
16
-
17
-
18
- class DeleteActor:
19
- _enabled = None
20
-
21
- def __init__(self, server):
22
- self._server = weakref.ref(server)
23
- self.deleted = deque()
24
- self.tags = []
25
- self.actor = None
26
-
27
- self.max_seen = 0
28
- self.n_tags = 0
29
- self.n_pings = 0
30
- self.n_nodes = 0
31
-
32
- @property
33
- def server(self):
34
- return self._server()
35
-
36
- async def tock_me(self):
37
- """
38
- Add the current tock to our buffer.
39
-
40
- This is updated whenever a new leader is selected.
41
- """
42
- self.tags.append(self.server.tock)
43
- self.tags = self.tags[-TAGS:]
44
- await self.actor.set_value((self.tags[0], self.tags[-1]))
45
-
46
- def add_deleted(self, nodes: NodeSet): # noqa: F821
47
- """
48
- These nodes are deleted. Remember them for some time.
49
- """
50
- if self.n_nodes == 0:
51
- return
52
- self.deleted.append((self.server.tock, nodes))
53
-
54
- def purge_to(self, tock):
55
- """
56
- Sufficient time has passed since this tock was seen, while all
57
- Delete actor nodes were active. Finally flush the entries that have
58
- been deleted before it.
59
- """
60
- while self.deleted and self.deleted[0][0] < tock:
61
- d = self.deleted.popleft()
62
- self.server.purge_deleted(d[1])
63
-
64
- async def enable(self, n):
65
- """
66
- Enable this actor, as a group of N.
67
- """
68
- if self.actor is None:
69
- self._enabled = True
70
- else:
71
- await self.actor.enable(n)
72
- self.n_tags = 0
73
- self.n_pings = 0
74
- self.n_nodes = n
75
-
76
- async def disable(self, n: int = 0):
77
- """
78
- Disable this actor. It will still listen, and require N Delete
79
- actor members in order to flush its deletion entries.
80
-
81
- Completely disable deletion flushing by passing n=0.
82
- """
83
- if self.actor is None:
84
- self._enabled = False
85
- else:
86
- await self.actor.disable()
87
- self.n_tags = 0
88
- self.n_pings = 0
89
- self.n_nodes = n
90
-
91
- async def run(self, evt: anyio.abc.Event = None):
92
- """
93
- The task that monitors the Delete actor.
94
- """
95
- try:
96
- T = get_transport("moat_kv")
97
- async with Actor(
98
- T(self.server.backend, *self.server.cfg.server.root, "del"),
99
- name=self.server.node.name,
100
- cfg=self.server.cfg.server.delete,
101
- enabled=False,
102
- ) as actor:
103
- self.actor = actor
104
- if self._enabled is not None:
105
- if self._enabled:
106
- await actor.enable()
107
- else:
108
- await actor.disable()
109
- if evt is not None:
110
- evt.set()
111
- async for evt in actor:
112
- if isinstance(evt, PingEvent):
113
- val = evt.value
114
- if val is None:
115
- self.n_pings = self.n_tags = 0
116
- continue
117
- if len(evt.msg.history) < self.n_nodes:
118
- self.n_pings = self.n_tags = 0
119
- continue
120
- self.n_pings += 1
121
- if self.n_pings > self.n_nodes:
122
- mx, self.max_seen = (
123
- self.max_seen,
124
- max(self.max_seen, val[1]),
125
- )
126
- if val[0] > mx > 0:
127
- await self.server.resync_deleted(evt.msg.history)
128
- continue
129
- self.purge_to(val[0])
130
- self.max_seen = max(self.max_seen, val[1])
131
-
132
- elif isinstance(evt, TagEvent):
133
- if actor.history_size == self.n_nodes:
134
- self.n_tags += 1
135
- if self.n_tags > 2:
136
- self.purge_to(self.tags[0])
137
- await self.tock_me()
138
- finally:
139
- self.actor = None