ga-clock 0.2.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.
ga_clock-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GA Clock contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: ga-clock
3
+ Version: 0.2.0
4
+ Summary: GA Clock is a controllable Python clock with realtime, accelerated, fixed, scheduled, and manual time modes.
5
+ Author: GA Clock contributors
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/andreagemma/clock#readme
8
+ Project-URL: Issues, https://github.com/andreagemma/clock/issues
9
+ Project-URL: Source, https://github.com/andreagemma/clock
10
+ Keywords: ga-clock,clock,scheduler,time,testing,simulation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest>=8.0; extra == "test"
25
+ Requires-Dist: pytest-cov>=5.0; extra == "test"
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.2; extra == "dev"
28
+ Requires-Dist: mypy>=1.10; extra == "dev"
29
+ Requires-Dist: pytest>=8.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.5; extra == "dev"
32
+ Requires-Dist: twine>=5.1; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # GA Clock
36
+
37
+ [![CI](https://github.com/andreagemma/clock/actions/workflows/ci.yml/badge.svg)](https://github.com/andreagemma/clock/actions/workflows/ci.yml)
38
+ [![PyPI](https://img.shields.io/pypi/v/ga-clock.svg)](https://pypi.org/project/ga-clock/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/ga-clock.svg)](https://pypi.org/project/ga-clock/)
40
+
41
+ GA Clock provides a controllable datetime source and an internal-time scheduler for
42
+ applications, simulations, and deterministic tests. A clock can follow wall time,
43
+ accelerate it, advance in fixed or manual steps, or jump directly between scheduled
44
+ events. The scheduler always uses the selected clock's internal time.
45
+
46
+ ## Installation
47
+
48
+ The PyPI distribution is named `ga-clock`; the import package is named `ga_clock`.
49
+
50
+ ```bash
51
+ python -m pip install ga-clock
52
+ ```
53
+
54
+ GA Clock has no runtime dependencies. Development and test tools are available as
55
+ extras:
56
+
57
+ ```bash
58
+ python -m pip install -e ".[test]"
59
+ python -m pip install -e ".[dev]"
60
+ ```
61
+
62
+ ## Quick Start
63
+
64
+ ```python
65
+ from datetime import datetime
66
+
67
+ from ga_clock import Clock
68
+
69
+ clock = Clock.manual(start_at=datetime(2026, 1, 1, 9, 0))
70
+ clock.step(hours=2)
71
+
72
+ assert clock.now() == datetime(2026, 1, 1, 11, 0)
73
+ assert clock.elapsed_hours() == 2
74
+ ```
75
+
76
+ When `start_at` is omitted, the clock starts at the current datetime. It also accepts
77
+ an ISO datetime string and an optional `tzinfo` object.
78
+
79
+ ## Clock Modes
80
+
81
+ | Mode | Internal-time behavior | `step()` behavior |
82
+ | --- | --- | --- |
83
+ | `realtime` | Advances at wall-clock speed | Runs due jobs without moving time directly |
84
+ | `wrap` | Advances at `factor * wall time` | Runs due jobs without moving time directly |
85
+ | `fixed` | Changes only when stepped | Advances by the configured fixed duration |
86
+ | `scheduled` | Changes only when stepped | Jumps to the next scheduled event |
87
+ | `manual` | Changes only when stepped | Advances by the supplied duration |
88
+
89
+ ### Realtime and Wrap
90
+
91
+ ```python
92
+ from ga_clock import Clock
93
+
94
+ realtime = Clock.realtime()
95
+ accelerated = Clock.wrap(factor=60)
96
+ ```
97
+
98
+ A wrap factor of `60` advances internal time by one minute for every elapsed wall-time
99
+ second. Factors must be greater than zero.
100
+
101
+ ### Fixed and Manual
102
+
103
+ ```python
104
+ from datetime import timedelta
105
+
106
+ from ga_clock import Clock
107
+
108
+ fixed = Clock.fixed(step=timedelta(minutes=15))
109
+ fixed.step().step()
110
+ assert fixed.elapsed_minutes() == 30
111
+
112
+ manual = Clock.manual()
113
+ manual.step(minutes=5).step(seconds=30)
114
+ assert manual.elapsed_seconds() == 330
115
+ ```
116
+
117
+ ### Scheduled
118
+
119
+ ```python
120
+ from datetime import datetime
121
+
122
+ from ga_clock import Clock
123
+
124
+ events: list[datetime] = []
125
+ clock = Clock.scheduled(start_at=datetime(2026, 1, 1, 9, 0))
126
+ clock.every().hour.do(lambda: events.append(clock.now()))
127
+
128
+ clock.step()
129
+
130
+ assert events == [datetime(2026, 1, 1, 10, 0)]
131
+ ```
132
+
133
+ Calling `step()` with no jobs scheduled is a no-op.
134
+
135
+ ## Scheduling
136
+
137
+ The fluent API is inspired by `schedule`, but all configuration operations return new
138
+ objects instead of mutating earlier builder values.
139
+
140
+ ```python
141
+ from datetime import datetime
142
+
143
+ from ga_clock import Clock
144
+
145
+ calls: list[str] = []
146
+ clock = Clock.manual(start_at=datetime(2026, 1, 1, 8, 0))
147
+
148
+ heartbeat = clock.every(10).seconds.do(lambda: calls.append("heartbeat"))
149
+ report = clock.every().monday.at("10:00").do(
150
+ lambda: calls.append("report")
151
+ ).tag("reports")
152
+
153
+ clock.step(seconds=10)
154
+ assert calls == ["heartbeat"]
155
+
156
+ clock.cancel(heartbeat)
157
+ clock.clear("reports")
158
+ assert clock.jobs() == []
159
+ ```
160
+
161
+ Supported units are seconds, minutes, hours, days, weeks, months, and years. Weekday
162
+ properties from `monday` through `sunday` are also available. `at()` accepts:
163
+
164
+ - `:SS` for minute jobs;
165
+ - `:MM` or `:MM:SS` for hourly jobs;
166
+ - `HH:MM` or `HH:MM:SS` for daily and larger jobs.
167
+
168
+ Returning `CancelJob` removes a job immediately after it runs:
169
+
170
+ ```python
171
+ from ga_clock import CancelJob
172
+
173
+
174
+ def run_once() -> object:
175
+ return CancelJob
176
+ ```
177
+
178
+ ## Elapsed Time
179
+
180
+ ```python
181
+ clock.elapsed() # datetime.timedelta
182
+ clock.elapsed_seconds() # float
183
+ clock.elapsed_minutes() # float
184
+ clock.elapsed_hours() # float
185
+ clock.elapsed_days() # float
186
+ clock.elapsed_months() # float
187
+ clock.elapsed_years() # float
188
+ clock.elapsed_values() # immutable Elapsed dataclass
189
+ ```
190
+
191
+ Months and years are duration approximations based on the average Gregorian year of
192
+ 365.2425 days; they are not calendar-boundary counts.
193
+
194
+ ## Errors
195
+
196
+ Invalid modes, factors, durations, schedule formats, and mode-specific operations raise
197
+ `ClockError`. It derives from both `GAClockError` and `ValueError`. Non-fatal package
198
+ warnings derive from `GAClockWarning`.
199
+
200
+ Scheduled job callbacks propagate their exceptions to the caller of `step()`,
201
+ `run_pending()`, or `run_all()`; GA Clock does not silently suppress callback failures.
202
+
203
+ ## Security
204
+
205
+ GA Clock does not deserialize data or load executable content. Scheduled callbacks are
206
+ ordinary Python callables and execute with the permissions of the current process. Only
207
+ schedule callbacks from trusted application code.
208
+
209
+ ## Development
210
+
211
+ GA Clock supports Python 3.10 and newer.
212
+
213
+ ```bash
214
+ python -m pip install -e ".[dev]"
215
+ python -m compileall -q src
216
+ python -m pytest --cov=ga_clock --cov-report=term-missing
217
+ ruff check .
218
+ mypy
219
+ python -m pip check
220
+ python -m build
221
+ python -m twine check dist/*
222
+ ```
223
+
224
+ ## Releases
225
+
226
+ `src/ga_clock/_version.py` is the only version source. To publish a release:
227
+
228
+ 1. Update `__version__` in `_version.py` and commit the release changes.
229
+ 2. Push `main` and wait for CI to pass.
230
+ 3. Configure the PyPI Trusted Publisher with project `ga-clock`, owner `andreagemma`,
231
+ repository `ga-clock`, workflow `release.yml`, and environment `pypi`.
232
+ 4. Run the **Create release** GitHub Actions workflow. With no override it creates the
233
+ `v<version>` tag, creates release notes, and explicitly dispatches the build and PyPI
234
+ publication workflow.
235
+
236
+ PyPI versions are immutable. Increment `_version.py` before publishing different
237
+ content.
238
+
239
+ ## License
240
+
241
+ GA Clock is distributed under the MIT License. See [LICENSE](LICENSE).
@@ -0,0 +1,207 @@
1
+ # GA Clock
2
+
3
+ [![CI](https://github.com/andreagemma/clock/actions/workflows/ci.yml/badge.svg)](https://github.com/andreagemma/clock/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/ga-clock.svg)](https://pypi.org/project/ga-clock/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/ga-clock.svg)](https://pypi.org/project/ga-clock/)
6
+
7
+ GA Clock provides a controllable datetime source and an internal-time scheduler for
8
+ applications, simulations, and deterministic tests. A clock can follow wall time,
9
+ accelerate it, advance in fixed or manual steps, or jump directly between scheduled
10
+ events. The scheduler always uses the selected clock's internal time.
11
+
12
+ ## Installation
13
+
14
+ The PyPI distribution is named `ga-clock`; the import package is named `ga_clock`.
15
+
16
+ ```bash
17
+ python -m pip install ga-clock
18
+ ```
19
+
20
+ GA Clock has no runtime dependencies. Development and test tools are available as
21
+ extras:
22
+
23
+ ```bash
24
+ python -m pip install -e ".[test]"
25
+ python -m pip install -e ".[dev]"
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```python
31
+ from datetime import datetime
32
+
33
+ from ga_clock import Clock
34
+
35
+ clock = Clock.manual(start_at=datetime(2026, 1, 1, 9, 0))
36
+ clock.step(hours=2)
37
+
38
+ assert clock.now() == datetime(2026, 1, 1, 11, 0)
39
+ assert clock.elapsed_hours() == 2
40
+ ```
41
+
42
+ When `start_at` is omitted, the clock starts at the current datetime. It also accepts
43
+ an ISO datetime string and an optional `tzinfo` object.
44
+
45
+ ## Clock Modes
46
+
47
+ | Mode | Internal-time behavior | `step()` behavior |
48
+ | --- | --- | --- |
49
+ | `realtime` | Advances at wall-clock speed | Runs due jobs without moving time directly |
50
+ | `wrap` | Advances at `factor * wall time` | Runs due jobs without moving time directly |
51
+ | `fixed` | Changes only when stepped | Advances by the configured fixed duration |
52
+ | `scheduled` | Changes only when stepped | Jumps to the next scheduled event |
53
+ | `manual` | Changes only when stepped | Advances by the supplied duration |
54
+
55
+ ### Realtime and Wrap
56
+
57
+ ```python
58
+ from ga_clock import Clock
59
+
60
+ realtime = Clock.realtime()
61
+ accelerated = Clock.wrap(factor=60)
62
+ ```
63
+
64
+ A wrap factor of `60` advances internal time by one minute for every elapsed wall-time
65
+ second. Factors must be greater than zero.
66
+
67
+ ### Fixed and Manual
68
+
69
+ ```python
70
+ from datetime import timedelta
71
+
72
+ from ga_clock import Clock
73
+
74
+ fixed = Clock.fixed(step=timedelta(minutes=15))
75
+ fixed.step().step()
76
+ assert fixed.elapsed_minutes() == 30
77
+
78
+ manual = Clock.manual()
79
+ manual.step(minutes=5).step(seconds=30)
80
+ assert manual.elapsed_seconds() == 330
81
+ ```
82
+
83
+ ### Scheduled
84
+
85
+ ```python
86
+ from datetime import datetime
87
+
88
+ from ga_clock import Clock
89
+
90
+ events: list[datetime] = []
91
+ clock = Clock.scheduled(start_at=datetime(2026, 1, 1, 9, 0))
92
+ clock.every().hour.do(lambda: events.append(clock.now()))
93
+
94
+ clock.step()
95
+
96
+ assert events == [datetime(2026, 1, 1, 10, 0)]
97
+ ```
98
+
99
+ Calling `step()` with no jobs scheduled is a no-op.
100
+
101
+ ## Scheduling
102
+
103
+ The fluent API is inspired by `schedule`, but all configuration operations return new
104
+ objects instead of mutating earlier builder values.
105
+
106
+ ```python
107
+ from datetime import datetime
108
+
109
+ from ga_clock import Clock
110
+
111
+ calls: list[str] = []
112
+ clock = Clock.manual(start_at=datetime(2026, 1, 1, 8, 0))
113
+
114
+ heartbeat = clock.every(10).seconds.do(lambda: calls.append("heartbeat"))
115
+ report = clock.every().monday.at("10:00").do(
116
+ lambda: calls.append("report")
117
+ ).tag("reports")
118
+
119
+ clock.step(seconds=10)
120
+ assert calls == ["heartbeat"]
121
+
122
+ clock.cancel(heartbeat)
123
+ clock.clear("reports")
124
+ assert clock.jobs() == []
125
+ ```
126
+
127
+ Supported units are seconds, minutes, hours, days, weeks, months, and years. Weekday
128
+ properties from `monday` through `sunday` are also available. `at()` accepts:
129
+
130
+ - `:SS` for minute jobs;
131
+ - `:MM` or `:MM:SS` for hourly jobs;
132
+ - `HH:MM` or `HH:MM:SS` for daily and larger jobs.
133
+
134
+ Returning `CancelJob` removes a job immediately after it runs:
135
+
136
+ ```python
137
+ from ga_clock import CancelJob
138
+
139
+
140
+ def run_once() -> object:
141
+ return CancelJob
142
+ ```
143
+
144
+ ## Elapsed Time
145
+
146
+ ```python
147
+ clock.elapsed() # datetime.timedelta
148
+ clock.elapsed_seconds() # float
149
+ clock.elapsed_minutes() # float
150
+ clock.elapsed_hours() # float
151
+ clock.elapsed_days() # float
152
+ clock.elapsed_months() # float
153
+ clock.elapsed_years() # float
154
+ clock.elapsed_values() # immutable Elapsed dataclass
155
+ ```
156
+
157
+ Months and years are duration approximations based on the average Gregorian year of
158
+ 365.2425 days; they are not calendar-boundary counts.
159
+
160
+ ## Errors
161
+
162
+ Invalid modes, factors, durations, schedule formats, and mode-specific operations raise
163
+ `ClockError`. It derives from both `GAClockError` and `ValueError`. Non-fatal package
164
+ warnings derive from `GAClockWarning`.
165
+
166
+ Scheduled job callbacks propagate their exceptions to the caller of `step()`,
167
+ `run_pending()`, or `run_all()`; GA Clock does not silently suppress callback failures.
168
+
169
+ ## Security
170
+
171
+ GA Clock does not deserialize data or load executable content. Scheduled callbacks are
172
+ ordinary Python callables and execute with the permissions of the current process. Only
173
+ schedule callbacks from trusted application code.
174
+
175
+ ## Development
176
+
177
+ GA Clock supports Python 3.10 and newer.
178
+
179
+ ```bash
180
+ python -m pip install -e ".[dev]"
181
+ python -m compileall -q src
182
+ python -m pytest --cov=ga_clock --cov-report=term-missing
183
+ ruff check .
184
+ mypy
185
+ python -m pip check
186
+ python -m build
187
+ python -m twine check dist/*
188
+ ```
189
+
190
+ ## Releases
191
+
192
+ `src/ga_clock/_version.py` is the only version source. To publish a release:
193
+
194
+ 1. Update `__version__` in `_version.py` and commit the release changes.
195
+ 2. Push `main` and wait for CI to pass.
196
+ 3. Configure the PyPI Trusted Publisher with project `ga-clock`, owner `andreagemma`,
197
+ repository `ga-clock`, workflow `release.yml`, and environment `pypi`.
198
+ 4. Run the **Create release** GitHub Actions workflow. With no override it creates the
199
+ `v<version>` tag, creates release notes, and explicitly dispatches the build and PyPI
200
+ publication workflow.
201
+
202
+ PyPI versions are immutable. Increment `_version.py` before publishing different
203
+ content.
204
+
205
+ ## License
206
+
207
+ GA Clock is distributed under the MIT License. See [LICENSE](LICENSE).
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ga-clock"
7
+ dynamic = ["version"]
8
+ description = "GA Clock is a controllable Python clock with realtime, accelerated, fixed, scheduled, and manual time modes."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "GA Clock contributors" }
15
+ ]
16
+ keywords = ["ga-clock", "clock", "scheduler", "time", "testing", "simulation"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Typing :: Typed"
27
+ ]
28
+ dependencies = []
29
+
30
+ [project.optional-dependencies]
31
+ test = [
32
+ "pytest>=8.0",
33
+ "pytest-cov>=5.0"
34
+ ]
35
+ dev = [
36
+ "build>=1.2",
37
+ "mypy>=1.10",
38
+ "pytest>=8.0",
39
+ "pytest-cov>=5.0",
40
+ "ruff>=0.5",
41
+ "twine>=5.1"
42
+ ]
43
+
44
+ [project.urls]
45
+ Documentation = "https://github.com/andreagemma/clock#readme"
46
+ Issues = "https://github.com/andreagemma/clock/issues"
47
+ Source = "https://github.com/andreagemma/clock"
48
+
49
+ [tool.setuptools]
50
+ package-dir = {"" = "src"}
51
+
52
+ [tool.setuptools.packages.find]
53
+ where = ["src"]
54
+
55
+ [tool.setuptools.package-data]
56
+ ga_clock = ["py.typed"]
57
+
58
+ [tool.setuptools.dynamic]
59
+ version = {attr = "ga_clock._version.__version__"}
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+ addopts = "-ra --strict-markers"
64
+
65
+ [tool.coverage.run]
66
+ branch = true
67
+ source = ["ga_clock"]
68
+
69
+ [tool.ruff]
70
+ line-length = 100
71
+ target-version = "py310"
72
+
73
+ [tool.ruff.lint]
74
+ select = ["E", "F", "I", "UP", "B", "SIM"]
75
+
76
+ [tool.mypy]
77
+ python_version = "3.10"
78
+ strict = true
79
+ files = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ """GA Clock: controllable clocks and internal-time scheduling."""
2
+
3
+ from ._api import CancelJob, Clock, Elapsed, Job
4
+ from ._version import __version__
5
+ from .exceptions import ClockError, GAClockError, GAClockWarning
6
+
7
+ __all__ = [
8
+ "CancelJob",
9
+ "Clock",
10
+ "ClockError",
11
+ "Elapsed",
12
+ "GAClockError",
13
+ "GAClockWarning",
14
+ "Job",
15
+ "__version__",
16
+ ]