doeff-time 0.1.0__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.
Files changed (39) hide show
  1. doeff_time-0.1.0/.gitignore +145 -0
  2. doeff_time-0.1.0/PKG-INFO +58 -0
  3. doeff_time-0.1.0/README.md +49 -0
  4. doeff_time-0.1.0/pyproject.toml +20 -0
  5. doeff_time-0.1.0/src/doeff_time/__init__.py +34 -0
  6. doeff_time-0.1.0/src/doeff_time/_internals/__init__.py +10 -0
  7. doeff_time-0.1.0/src/doeff_time/_internals/deadline.py +14 -0
  8. doeff_time-0.1.0/src/doeff_time/_internals/sim_clock.py +24 -0
  9. doeff_time-0.1.0/src/doeff_time/_internals/time_queue.py +68 -0
  10. doeff_time-0.1.0/src/doeff_time/_internals/validation.py +12 -0
  11. doeff_time-0.1.0/src/doeff_time/effects/__init__.py +29 -0
  12. doeff_time-0.1.0/src/doeff_time/effects/time.py +261 -0
  13. doeff_time-0.1.0/src/doeff_time/epoch_ms.py +21 -0
  14. doeff_time-0.1.0/src/doeff_time/handlers/__init__.py +13 -0
  15. doeff_time-0.1.0/src/doeff_time/handlers/_wall_ticks.py +59 -0
  16. doeff_time-0.1.0/src/doeff_time/handlers/async_time.py +142 -0
  17. doeff_time-0.1.0/src/doeff_time/handlers/sim_time.py +328 -0
  18. doeff_time-0.1.0/src/doeff_time/handlers/sync_time.py +127 -0
  19. doeff_time-0.1.0/src/doeff_time/py.typed +1 -0
  20. doeff_time-0.1.0/tests/conftest.py +38 -0
  21. doeff_time-0.1.0/tests/test_async_handler.py +93 -0
  22. doeff_time-0.1.0/tests/test_datetime_validation.py +30 -0
  23. doeff_time-0.1.0/tests/test_delay.py +15 -0
  24. doeff_time-0.1.0/tests/test_get_time.py +24 -0
  25. doeff_time-0.1.0/tests/test_race_within.py +122 -0
  26. doeff_time-0.1.0/tests/test_sim_close_out.py +74 -0
  27. doeff_time-0.1.0/tests/test_sim_daemon_pattern.py +237 -0
  28. doeff_time-0.1.0/tests/test_sim_many_services.py +212 -0
  29. doeff_time-0.1.0/tests/test_sim_migration_readiness.py +756 -0
  30. doeff_time-0.1.0/tests/test_sim_step_budget.py +147 -0
  31. doeff_time-0.1.0/tests/test_sim_time.py +356 -0
  32. doeff_time-0.1.0/tests/test_sim_time_effect_type_filter.py +98 -0
  33. doeff_time-0.1.0/tests/test_time_contract.hy +185 -0
  34. doeff_time-0.1.0/tests/test_time_queue.py +40 -0
  35. doeff_time-0.1.0/tests/test_wait_ticks.py +205 -0
  36. doeff_time-0.1.0/tests/test_wait_until.py +18 -0
  37. doeff_time-0.1.0/tests/test_wait_within.py +220 -0
  38. doeff_time-0.1.0/tests/time_contract_clocks.hy +68 -0
  39. doeff_time-0.1.0/tests/time_test_support.py +80 -0
@@ -0,0 +1,145 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ # macOS debug symbols for the compiled extension above: build output that
7
+ # embeds the builder's absolute paths, so it must not be distributed either.
8
+ *.dSYM/
9
+ .Python
10
+ env/
11
+ venv/
12
+ .venv/
13
+ ENV/
14
+ env.bak/
15
+ venv.bak/
16
+ .env
17
+ .env.*
18
+
19
+ # Distribution / packaging
20
+ .Python
21
+ build/
22
+ develop-eggs/
23
+ dist/
24
+ downloads/
25
+ eggs/
26
+ .eggs/
27
+ lib/
28
+ lib64/
29
+ parts/
30
+ sdist/
31
+ var/
32
+ wheels/
33
+ *.egg-info/
34
+ .installed.cfg
35
+ *.egg
36
+ MANIFEST
37
+
38
+ # docs の site(make docs-site・mkdocs build の出力)
39
+ /site/
40
+
41
+ # Testing
42
+ .pytest_cache/
43
+ .coverage
44
+ .coverage.*
45
+ coverage.xml
46
+ *.cover
47
+ .hypothesis/
48
+ htmlcov/
49
+ .tox/
50
+
51
+ # IDE
52
+ # Generated by Eclipse buildship and machine-specific: embeds the local Java home
53
+ # and toolchain paths. The gradle build does not read it.
54
+ ide-plugins/pycharm/.settings/org.eclipse.buildship.core.prefs
55
+ .idea/
56
+ .vscode/
57
+ *.swp
58
+ *.swo
59
+ *~
60
+ .DS_Store
61
+
62
+ # UV
63
+ !uv.lock
64
+ .venv/
65
+
66
+ # Ruff
67
+ .ruff_cache/
68
+
69
+ # MyPy
70
+ .mypy_cache/
71
+ .dmypy.json
72
+ dmypy.json
73
+
74
+ # Generated logs, traces, and local databases
75
+ .doeff-lint.jsonl
76
+ .doeff-flow/
77
+ *.jsonl
78
+ # 旧い版の coordinator の置き場の log(版上げの下見の検 test_deployed_state_upgrade.hy が読む fixture — 生成された log ではない)
79
+ !packages/doeff-cluster/tests/fixtures/coordinator_state_*/wal/wal.jsonl
80
+ # codex の app-server の実物の行を録った物(行の分類の検 packages/doeff-codex/tests/test_lines.hy が読む fixture — 生成された log ではない)
81
+ !packages/doeff-codex/tests/recorded/codex-*/*.jsonl
82
+ *.db
83
+ *.db-journal
84
+ *.db-shm
85
+ *.db-wal
86
+ *.sqlite
87
+ *.sqlite-journal
88
+ *.sqlite-shm
89
+ *.sqlite-wal
90
+ *.sqlite3
91
+ *.sqlite3-journal
92
+ *.sqlite3-shm
93
+ *.sqlite3-wal
94
+ None
95
+ Untitled
96
+
97
+ # Local agent and browser automation state
98
+ .agent-home/
99
+ .playwright-mcp/
100
+ .claude/settings.local.json
101
+ .claude/skills/install-vscode-plugin/
102
+ .agents/skills/install-vscode-plugin/
103
+
104
+ # Accidental desktop helper bundle captured at repository root
105
+ /server.js
106
+
107
+ # Rust build artifacts
108
+ rust/**/target/
109
+ packages/**/target/
110
+
111
+ # Local deploy keys
112
+ proboscis_doeff_deploy
113
+ proboscis_doeff_deploy.pub
114
+
115
+ # Worktrees and orchestration
116
+ .git-worktrees/
117
+ .orch/
118
+ .sisyphus/
119
+ VAULT
120
+
121
+ # Override global gitignore for examples
122
+ !examples/
123
+ ORCH_PROMPT.md
124
+ .worktrees/
125
+
126
+ # Local workspace artifacts
127
+ # Repo-root scratch directory only. A `python/` directory inside a package
128
+ # (e.g. packages/doeff-indexer/python/) is NOT covered by this rule.
129
+ /python/
130
+
131
+
132
+ # 設計記録は repo に入れる — global の除外(`artifacts` の dir・`*.html`・`*.svg`)を打ち消す
133
+ # (打ち消しは dir を先に戻さないと中身に効かない)
134
+ !docs/design/*/artifacts/
135
+ !docs/design/**
136
+ # 書き込みロックだけは入れない(道具が作る一時 file — 打ち消しの後に置く)
137
+ docs/design/*/.records.lock
138
+
139
+ # Repository-owned Zenn article visuals (override global image ignores)
140
+ !/images/zenn-use-cases-v0/*.png
141
+ # 記事が参照する採用画像は generated/ の下に在る(上の行は直下だけに効き、2026-09-16 の 47 枚は入らなかった)
142
+ !/images/zenn-use-cases-v0/generated/
143
+ !/images/zenn-use-cases-v0/generated/*.png
144
+ # 手で書いた図の正本(global の `*.svg` の除外を打ち消す)
145
+ !/publications/zenn-use-cases-v0/visuals/*.svg
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.5
2
+ Name: doeff-time
3
+ Version: 0.1.0
4
+ Summary: Provider-agnostic time effects for doeff
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: doeff-core-effects<0.9,>=0.8.0
7
+ Requires-Dist: doeff<0.9,>=0.8.0
8
+ Description-Content-Type: text/markdown
9
+
10
+ # doeff-time
11
+
12
+ Provider-agnostic time effects for `doeff`.
13
+
14
+ ## Effects
15
+
16
+ - `Delay(seconds)`
17
+ - `WaitUntil(target: datetime)` (`target` must be timezone-aware)
18
+ - `GetTime()`
19
+ - `GetMonotonic()` — monotonic seconds (`float`); only differences are meaningful
20
+ - `ScheduleAt(time: datetime, program)` (`time` must be timezone-aware) — answers the spawned `Task`;
21
+ `Wait(task)` answers `program`'s value, or raises its failure
22
+ - `SetTime(time: datetime)` (for simulation handlers, `time` must be timezone-aware)
23
+
24
+ ## Conversions
25
+
26
+ - `epoch_ms_of(at: datetime) -> int` — timezone-aware time → epoch milliseconds, floored by integer
27
+ `timedelta` division (no float on the way). This is the one rounding for every stamp that is laid
28
+ next to another stamp: two places rounding the same instant differently (round vs floor) make a
29
+ write at 329.6 ms look 1 ms later than a read at 329.9 ms.
30
+
31
+ ## Handlers
32
+
33
+ - `async_time_handler()` for `asyncio` runtimes
34
+ - `sync_time_handler()` for blocking runtimes
35
+ - `sim_time_handler(start_time=...)` for deterministic virtual time (`start_time` is timezone-aware
36
+ `datetime`), or `sim_time_handler(clock=SimClock(start))` when the caller wants to read or move
37
+ the virtual clock outside the program (tests)
38
+
39
+ ### Shared contract (all three handlers)
40
+
41
+ `tests/test_time_contract.hy` runs the same deftests under each handler (`:interpreters
42
+ ["async" "sync" "sim"]`): readings never go back, `Delay(d)` / `WaitUntil(t)` advance the clock by
43
+ `d` / up to `t` (exactly on the virtual clock, with a small jitter bound on the wall clock), past
44
+ targets return at once, other effects pass through, and `ScheduleAt` tasks run in time order and
45
+ answer their program's value or failure through `Wait`. `SetTime` is simulation-only and stays out
46
+ of the shared contract. Note: the scheduled program runs under the handlers outside the time
47
+ handler, so it cannot itself perform time effects.
48
+
49
+ ### Virtual time contract (`sim_time_handler`)
50
+
51
+ - Install inside `scheduled(...)` and outside every `Spawn` whose tasks sleep on it; spawned tasks
52
+ inherit the handler and share one clock.
53
+ - Time advances to the earliest pending wake-up only when no normal-priority task is runnable, so a
54
+ task woken at `t` reads `t` however many scheduler steps it takes.
55
+ - Wake-ups at the same instant resume in the order they were requested; the trace is identical on
56
+ the Rust scheduler (the default) and the Python one (`DOEFF_SCHEDULER=python`).
57
+ - The run ends when the root program returns, even while daemon services are still sleeping.
58
+ - `GetMonotonic()` answers the virtual clock's POSIX timestamp.
@@ -0,0 +1,49 @@
1
+ # doeff-time
2
+
3
+ Provider-agnostic time effects for `doeff`.
4
+
5
+ ## Effects
6
+
7
+ - `Delay(seconds)`
8
+ - `WaitUntil(target: datetime)` (`target` must be timezone-aware)
9
+ - `GetTime()`
10
+ - `GetMonotonic()` — monotonic seconds (`float`); only differences are meaningful
11
+ - `ScheduleAt(time: datetime, program)` (`time` must be timezone-aware) — answers the spawned `Task`;
12
+ `Wait(task)` answers `program`'s value, or raises its failure
13
+ - `SetTime(time: datetime)` (for simulation handlers, `time` must be timezone-aware)
14
+
15
+ ## Conversions
16
+
17
+ - `epoch_ms_of(at: datetime) -> int` — timezone-aware time → epoch milliseconds, floored by integer
18
+ `timedelta` division (no float on the way). This is the one rounding for every stamp that is laid
19
+ next to another stamp: two places rounding the same instant differently (round vs floor) make a
20
+ write at 329.6 ms look 1 ms later than a read at 329.9 ms.
21
+
22
+ ## Handlers
23
+
24
+ - `async_time_handler()` for `asyncio` runtimes
25
+ - `sync_time_handler()` for blocking runtimes
26
+ - `sim_time_handler(start_time=...)` for deterministic virtual time (`start_time` is timezone-aware
27
+ `datetime`), or `sim_time_handler(clock=SimClock(start))` when the caller wants to read or move
28
+ the virtual clock outside the program (tests)
29
+
30
+ ### Shared contract (all three handlers)
31
+
32
+ `tests/test_time_contract.hy` runs the same deftests under each handler (`:interpreters
33
+ ["async" "sync" "sim"]`): readings never go back, `Delay(d)` / `WaitUntil(t)` advance the clock by
34
+ `d` / up to `t` (exactly on the virtual clock, with a small jitter bound on the wall clock), past
35
+ targets return at once, other effects pass through, and `ScheduleAt` tasks run in time order and
36
+ answer their program's value or failure through `Wait`. `SetTime` is simulation-only and stays out
37
+ of the shared contract. Note: the scheduled program runs under the handlers outside the time
38
+ handler, so it cannot itself perform time effects.
39
+
40
+ ### Virtual time contract (`sim_time_handler`)
41
+
42
+ - Install inside `scheduled(...)` and outside every `Spawn` whose tasks sleep on it; spawned tasks
43
+ inherit the handler and share one clock.
44
+ - Time advances to the earliest pending wake-up only when no normal-priority task is runnable, so a
45
+ task woken at `t` reads `t` however many scheduler steps it takes.
46
+ - Wake-ups at the same instant resume in the order they were requested; the trace is identical on
47
+ the Rust scheduler (the default) and the Python one (`DOEFF_SCHEDULER=python`).
48
+ - The run ends when the root program returns, even while daemon services are still sleeping.
49
+ - `GetMonotonic()` answers the virtual clock's POSIX timestamp.
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "doeff-time"
7
+ version = "0.1.0"
8
+ description = "Provider-agnostic time effects for doeff"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "doeff>=0.8.0,<0.9",
13
+ "doeff-core-effects>=0.8.0,<0.9",
14
+ ]
15
+
16
+ [tool.hatch.build.targets.wheel]
17
+ packages = ["src/doeff_time"]
18
+
19
+ [tool.uv.sources]
20
+ doeff = { workspace = true }
@@ -0,0 +1,34 @@
1
+ """Public API for doeff-time."""
2
+
3
+ from doeff_time._internals.sim_clock import SimClock as SimClock
4
+ from doeff_time.effects.time import Delay as Delay
5
+ from doeff_time.effects.time import DelayEffect as DelayEffect
6
+ from doeff_time.effects.time import GetMonotonic as GetMonotonic
7
+ from doeff_time.effects.time import GetMonotonicEffect as GetMonotonicEffect
8
+ from doeff_time.effects.time import GetTime as GetTime
9
+ from doeff_time.effects.time import GetTimeEffect as GetTimeEffect
10
+ from doeff_time.effects.time import RaceOutcome as RaceOutcome
11
+ from doeff_time.effects.time import RaceWithin as RaceWithin
12
+ from doeff_time.effects.time import RaceWithinEffect as RaceWithinEffect
13
+ from doeff_time.effects.time import ScheduleAt as ScheduleAt
14
+ from doeff_time.effects.time import ScheduleAtEffect as ScheduleAtEffect
15
+ from doeff_time.effects.time import SetTime as SetTime
16
+ from doeff_time.effects.time import SetTimeEffect as SetTimeEffect
17
+ from doeff_time.effects.time import TicksOutcome as TicksOutcome
18
+ from doeff_time.effects.time import WaitTicks as WaitTicks
19
+ from doeff_time.effects.time import WaitTicksEffect as WaitTicksEffect
20
+ from doeff_time.effects.time import WaitUntil as WaitUntil
21
+ from doeff_time.effects.time import WaitUntilEffect as WaitUntilEffect
22
+ from doeff_time.effects.time import WaitWithin as WaitWithin
23
+ from doeff_time.effects.time import WaitWithinEffect as WaitWithinEffect
24
+ from doeff_time.effects.time import delay as delay
25
+ from doeff_time.effects.time import get_monotonic as get_monotonic
26
+ from doeff_time.effects.time import get_time as get_time
27
+ from doeff_time.effects.time import schedule_at as schedule_at
28
+ from doeff_time.effects.time import set_time as set_time
29
+ from doeff_time.effects.time import wait_until as wait_until
30
+ from doeff_time.effects.time import wait_within as wait_within
31
+ from doeff_time.epoch_ms import epoch_ms_of as epoch_ms_of
32
+ from doeff_time.handlers.async_time import async_time_handler as async_time_handler
33
+ from doeff_time.handlers.sim_time import sim_time_handler as sim_time_handler
34
+ from doeff_time.handlers.sync_time import sync_time_handler as sync_time_handler
@@ -0,0 +1,10 @@
1
+ """Internal helpers for simulated time."""
2
+
3
+ from .sim_clock import SimClock
4
+ from .time_queue import TimeQueue, TimeQueueEntry
5
+
6
+ __all__ = [
7
+ "SimClock",
8
+ "TimeQueue",
9
+ "TimeQueueEntry",
10
+ ]
@@ -0,0 +1,14 @@
1
+ """The value a timed wait's deadline completes with."""
2
+
3
+
4
+ class _DeadlinePassed:
5
+ """The deadline of a timed wait passed. Told apart from every future's value by identity (a future may
6
+ complete with None — RaceWithin), so the clock handlers complete their deadline with this, never None."""
7
+
8
+ __slots__ = ()
9
+
10
+ def __repr__(self) -> str:
11
+ return "DEADLINE_PASSED"
12
+
13
+
14
+ DEADLINE_PASSED = _DeadlinePassed()
@@ -0,0 +1,24 @@
1
+ """Virtual monotonic clock for simulation handlers."""
2
+
3
+
4
+ from dataclasses import dataclass
5
+ from datetime import datetime, timezone
6
+
7
+ from .validation import ensure_aware_datetime
8
+
9
+
10
+ @dataclass
11
+ class SimClock:
12
+ current_time: datetime = datetime(1970, 1, 1, tzinfo=timezone.utc)
13
+
14
+ def __post_init__(self) -> None:
15
+ self.current_time = ensure_aware_datetime(self.current_time, name="current_time")
16
+
17
+ def advance_to(self, target_time: datetime) -> datetime:
18
+ target = ensure_aware_datetime(target_time, name="target_time")
19
+ self.current_time = max(self.current_time, target)
20
+ return self.current_time
21
+
22
+ def set_time(self, new_time: datetime) -> datetime:
23
+ self.current_time = ensure_aware_datetime(new_time, name="new_time")
24
+ return self.current_time
@@ -0,0 +1,68 @@
1
+ """Min-heap queue of time-ordered promises."""
2
+
3
+ import heapq
4
+ from dataclasses import dataclass
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from doeff_core_effects.scheduler import Promise
9
+
10
+ from doeff_time._internals.validation import ensure_aware_datetime
11
+
12
+ # ``Promise`` is generic to the type checker but not subscriptable at runtime.
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class TimeQueueEntry:
17
+ time: datetime
18
+ sequence: int
19
+ promise: "Promise[Any]"
20
+ # The value the clock completes ``promise`` with (a RaceWithin deadline's DEADLINE_PASSED, else None).
21
+ answer: Any = None
22
+
23
+
24
+ class TimeQueue:
25
+ def __init__(self) -> None:
26
+ self._mut_sequence = 0
27
+ self._mut_items: "list[tuple[datetime, int, Promise[Any], Any]]" = [] # noqa: UP037
28
+ # Sequences of entries withdrawn before their time (a timed wait whose future won —
29
+ # agora-redesign #2618). Dropped lazily when they reach the head, so withdrawing is O(1)
30
+ # and the clock never advances to a deadline nobody waits for.
31
+ self._mut_withdrawn: set[int] = set()
32
+ # Sequences still in the heap and not withdrawn — answers "is it still queued?" in O(1).
33
+ self._mut_queued: set[int] = set()
34
+
35
+ def push(self, time: datetime, promise: "Promise[Any]", answer: Any = None) -> int:
36
+ """Queue ``promise`` to be completed with ``answer`` at ``time``; answers the entry's sequence (for ``withdraw``)."""
37
+ target_time = ensure_aware_datetime(time, name="time")
38
+ self._mut_sequence += 1
39
+ heapq.heappush(self._mut_items, (target_time, self._mut_sequence, promise, answer))
40
+ self._mut_queued.add(self._mut_sequence)
41
+ return self._mut_sequence
42
+
43
+ def withdraw(self, sequence: int) -> None:
44
+ """Drop a queued entry before its time (no-op when it was already popped)."""
45
+ if sequence in self._mut_queued:
46
+ self._mut_queued.discard(sequence)
47
+ self._mut_withdrawn.add(sequence)
48
+ self._drop_withdrawn_head()
49
+
50
+ def _drop_withdrawn_head(self) -> None:
51
+ """Keep the head a live entry, so ``pop`` / ``empty`` never see a withdrawn deadline."""
52
+ while self._mut_items and self._mut_items[0][1] in self._mut_withdrawn:
53
+ _, sequence, _, _ = heapq.heappop(self._mut_items)
54
+ self._mut_withdrawn.discard(sequence)
55
+
56
+ def pop(self) -> TimeQueueEntry:
57
+ self._drop_withdrawn_head()
58
+ time, sequence, promise, answer = heapq.heappop(self._mut_items)
59
+ self._mut_queued.discard(sequence)
60
+ self._drop_withdrawn_head()
61
+ return TimeQueueEntry(time=time, sequence=sequence, promise=promise, answer=answer)
62
+
63
+ def empty(self) -> bool:
64
+ self._drop_withdrawn_head()
65
+ return not self._mut_items
66
+
67
+ def __len__(self) -> int:
68
+ return len(self._mut_queued)
@@ -0,0 +1,12 @@
1
+ """Internal validation helpers for doeff-time."""
2
+
3
+
4
+ from datetime import datetime
5
+
6
+
7
+ def ensure_aware_datetime(value: datetime, *, name: str) -> datetime:
8
+ if not isinstance(value, datetime):
9
+ raise TypeError(f"{name} must be datetime, got {type(value).__name__}")
10
+ if value.tzinfo is None:
11
+ raise ValueError(f"{name} must be timezone-aware datetime")
12
+ return value
@@ -0,0 +1,29 @@
1
+ """Public time effects for doeff-time."""
2
+
3
+ from doeff_time.effects.time import Delay as Delay
4
+ from doeff_time.effects.time import DelayEffect as DelayEffect
5
+ from doeff_time.effects.time import GetMonotonic as GetMonotonic
6
+ from doeff_time.effects.time import GetMonotonicEffect as GetMonotonicEffect
7
+ from doeff_time.effects.time import GetTime as GetTime
8
+ from doeff_time.effects.time import GetTimeEffect as GetTimeEffect
9
+ from doeff_time.effects.time import RaceOutcome as RaceOutcome
10
+ from doeff_time.effects.time import RaceWithin as RaceWithin
11
+ from doeff_time.effects.time import RaceWithinEffect as RaceWithinEffect
12
+ from doeff_time.effects.time import ScheduleAt as ScheduleAt
13
+ from doeff_time.effects.time import ScheduleAtEffect as ScheduleAtEffect
14
+ from doeff_time.effects.time import SetTime as SetTime
15
+ from doeff_time.effects.time import SetTimeEffect as SetTimeEffect
16
+ from doeff_time.effects.time import TicksOutcome as TicksOutcome
17
+ from doeff_time.effects.time import WaitTicks as WaitTicks
18
+ from doeff_time.effects.time import WaitTicksEffect as WaitTicksEffect
19
+ from doeff_time.effects.time import WaitUntil as WaitUntil
20
+ from doeff_time.effects.time import WaitUntilEffect as WaitUntilEffect
21
+ from doeff_time.effects.time import WaitWithin as WaitWithin
22
+ from doeff_time.effects.time import WaitWithinEffect as WaitWithinEffect
23
+ from doeff_time.effects.time import delay as delay
24
+ from doeff_time.effects.time import get_monotonic as get_monotonic
25
+ from doeff_time.effects.time import get_time as get_time
26
+ from doeff_time.effects.time import schedule_at as schedule_at
27
+ from doeff_time.effects.time import set_time as set_time
28
+ from doeff_time.effects.time import wait_until as wait_until
29
+ from doeff_time.effects.time import wait_within as wait_within