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 +21 -0
- ga_clock-0.2.0/PKG-INFO +241 -0
- ga_clock-0.2.0/README.md +207 -0
- ga_clock-0.2.0/pyproject.toml +79 -0
- ga_clock-0.2.0/setup.cfg +4 -0
- ga_clock-0.2.0/src/ga_clock/__init__.py +16 -0
- ga_clock-0.2.0/src/ga_clock/_api.py +720 -0
- ga_clock-0.2.0/src/ga_clock/_version.py +3 -0
- ga_clock-0.2.0/src/ga_clock/exceptions.py +15 -0
- ga_clock-0.2.0/src/ga_clock/py.typed +1 -0
- ga_clock-0.2.0/src/ga_clock.egg-info/PKG-INFO +241 -0
- ga_clock-0.2.0/src/ga_clock.egg-info/SOURCES.txt +15 -0
- ga_clock-0.2.0/src/ga_clock.egg-info/dependency_links.txt +1 -0
- ga_clock-0.2.0/src/ga_clock.egg-info/requires.txt +12 -0
- ga_clock-0.2.0/src/ga_clock.egg-info/top_level.txt +1 -0
- ga_clock-0.2.0/tests/test_api.py +254 -0
- ga_clock-0.2.0/tests/test_version.py +11 -0
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.
|
ga_clock-0.2.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/andreagemma/clock/actions/workflows/ci.yml)
|
|
38
|
+
[](https://pypi.org/project/ga-clock/)
|
|
39
|
+
[](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).
|
ga_clock-0.2.0/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# GA Clock
|
|
2
|
+
|
|
3
|
+
[](https://github.com/andreagemma/clock/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/ga-clock/)
|
|
5
|
+
[](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"]
|
ga_clock-0.2.0/setup.cfg
ADDED
|
@@ -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
|
+
]
|