clock-pattern 0.7.0__tar.gz → 0.9.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 (31) hide show
  1. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/.gitignore +3 -0
  2. clock_pattern-0.9.0/PKG-INFO +354 -0
  3. clock_pattern-0.9.0/README.md +329 -0
  4. clock_pattern-0.9.0/clock_pattern/__init__.py +19 -0
  5. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/clock_pattern/clocks/__init__.py +2 -0
  6. clock_pattern-0.9.0/clock_pattern/clocks/models/clock.py +66 -0
  7. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/clock_pattern/clocks/system_clock.py +26 -18
  8. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/clock_pattern/clocks/testing/fixed_clock.py +15 -12
  9. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/clock_pattern/clocks/testing/mock_clock.py +32 -25
  10. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/clock_pattern/clocks/utc_clock.py +6 -3
  11. clock_pattern-0.9.0/clock_pattern/monotonic_clocks/__init__.py +7 -0
  12. clock_pattern-0.9.0/clock_pattern/monotonic_clocks/models/__init__.py +3 -0
  13. clock_pattern-0.9.0/clock_pattern/monotonic_clocks/models/monotonic_clock.py +48 -0
  14. clock_pattern-0.9.0/clock_pattern/monotonic_clocks/system_monotonic_clock.py +48 -0
  15. clock_pattern-0.9.0/clock_pattern/sleepers/__init__.py +10 -0
  16. clock_pattern-0.9.0/clock_pattern/sleepers/models/__init__.py +7 -0
  17. clock_pattern-0.9.0/clock_pattern/sleepers/models/sleeper.py +62 -0
  18. clock_pattern-0.9.0/clock_pattern/sleepers/models/sleeper_async.py +62 -0
  19. clock_pattern-0.9.0/clock_pattern/sleepers/system_sleeper.py +139 -0
  20. clock_pattern-0.9.0/clock_pattern/sleepers/system_sleeper_async.py +133 -0
  21. clock_pattern-0.9.0/clock_pattern/stopwatches/__init__.py +3 -0
  22. clock_pattern-0.9.0/clock_pattern/stopwatches/stopwatch.py +144 -0
  23. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/pyproject.toml +2 -1
  24. clock_pattern-0.7.0/PKG-INFO +0 -208
  25. clock_pattern-0.7.0/README.md +0 -183
  26. clock_pattern-0.7.0/clock_pattern/__init__.py +0 -10
  27. clock_pattern-0.7.0/clock_pattern/models/clock.py +0 -59
  28. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/LICENSE.md +0 -0
  29. {clock_pattern-0.7.0/clock_pattern → clock_pattern-0.9.0/clock_pattern/clocks}/models/__init__.py +0 -0
  30. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/clock_pattern/clocks/testing/__init__.py +0 -0
  31. {clock_pattern-0.7.0 → clock_pattern-0.9.0}/clock_pattern/py.typed +0 -0
@@ -1,5 +1,7 @@
1
1
  # linting files
2
2
  .coverage
3
+ coverage/
4
+ htmlcov/
3
5
 
4
6
  # cache files/folders
5
7
  **cache**
@@ -10,3 +12,4 @@
10
12
 
11
13
  # environment folders
12
14
  **.venv**
15
+ **.agents/skills**
@@ -0,0 +1,354 @@
1
+ Metadata-Version: 2.5
2
+ Name: clock-pattern
3
+ Version: 0.9.0
4
+ Summary: The Clock Pattern is a Python package that turns time into an injectable dependency.
5
+ Project-URL: Homepage, https://github.com/adriamontoto/clock-pattern
6
+ Project-URL: Repository, https://github.com/adriamontoto/clock-pattern
7
+ Project-URL: Issues, https://github.com/adriamontoto/clock-pattern/issues
8
+ Author: Adria Montoto
9
+ License-Expression: MIT
10
+ License-File: LICENSE.md
11
+ Keywords: clock,dependency-injection,development,domain-driven-design,pattern,python,utilities
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: value-object-pattern>=1.31.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ <a name="readme-top"></a>
27
+
28
+ # 🕰️ Clock Pattern
29
+
30
+ <p align="center">
31
+ <a href="https://github.com/adriamontoto/clock-pattern/actions/workflows/ci.yaml?event=push&branch=master" target="_blank">
32
+ <img src="https://github.com/adriamontoto/clock-pattern/actions/workflows/ci.yaml/badge.svg?event=push&branch=master" alt="CI Pipeline">
33
+ </a>
34
+ <a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/adriamontoto/clock-pattern" target="_blank">
35
+ <img src="https://coverage-badge.samuelcolvin.workers.dev/adriamontoto/clock-pattern.svg" alt="Coverage Pipeline">
36
+ </a>
37
+ <a href="https://pypi.org/project/clock-pattern" target="_blank">
38
+ <img src="https://img.shields.io/pypi/v/clock-pattern?color=%2334D058&label=pypi%20package" alt="Package Version">
39
+ </a>
40
+ <a href="https://pypi.org/project/clock-pattern/" target="_blank">
41
+ <img src="https://img.shields.io/pypi/pyversions/clock-pattern.svg?color=%2334D058" alt="Supported Python Versions">
42
+ </a>
43
+ <a href="https://pepy.tech/projects/clock-pattern" target="_blank">
44
+ <img src="https://static.pepy.tech/badge/clock-pattern/month" alt="Package Downloads">
45
+ </a>
46
+ <a href="https://deepwiki.com/adriamontoto/clock-pattern" target="_blank">
47
+ <img src="https://img.shields.io/badge/DeepWiki-adriamontoto%2Fclock--pattern-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==" alt="Project Documentation">
48
+ </a>
49
+ </p>
50
+
51
+ The **Clock Pattern** is a Python 🐍 package that turns time into an injectable dependency 🧩. Instead of scattering
52
+ `datetime.now()` or `date.today()` through application code, domain services depend on a small `Clock` interface. That
53
+ keeps time-sensitive logic deterministic in tests, makes timezone choices explicit, and lets production code swap clock
54
+ implementations without touching business rules.
55
+ <br><br>
56
+
57
+ ## Table of Contents
58
+
59
+ - [📥 Installation](#installation)
60
+ - [📚 Documentation](#documentation)
61
+ - [⚡ Quick Start](#quick-start)
62
+ - [🧩 Why Inject a Clock?](#why-inject-a-clock)
63
+ - [📚 Available Clocks](#available-clocks)
64
+ - [🌍 Timezone Behavior](#timezone-behavior)
65
+ - [🧪 Testing Time-Sensitive Code](#testing-time-sensitive-code)
66
+ - [🎄 Real-Life Case: Christmas Detector Service](#real-life-case-christmas-detector-service)
67
+ - [🤝 Contributing](#contributing)
68
+ - [🔑 License](#license)
69
+
70
+ <p align="right">
71
+ <a href="#readme-top">🔼 Back to top</a>
72
+ </p><br><br>
73
+
74
+ <a name="installation"></a>
75
+
76
+ ## 📥 Installation
77
+
78
+ You can install **Clock Pattern** using `pip`:
79
+
80
+ ```bash
81
+ pip install clock-pattern
82
+ ```
83
+
84
+ You can install the companion AI-agent skill from [skills.sh](https://www.skills.sh/) with Vercel's `skills` CLI:
85
+
86
+ ```bash
87
+ npx skills add adriamontoto/clock-pattern
88
+ ```
89
+
90
+ Review the skill source in [`skills/clock-pattern`](skills/clock-pattern) before installing it in sensitive
91
+ environments.
92
+
93
+ <p align="right">
94
+ <a href="#readme-top">🔼 Back to top</a>
95
+ </p><br><br>
96
+
97
+ <a name="documentation"></a>
98
+
99
+ ## 📚 Documentation
100
+
101
+ The root README is the entry point. Deeper guides live in this repository and are linked here:
102
+
103
+ - [`docs/README.md`](docs/README.md): Documentation hub.
104
+ - [`docs/usage/README.md`](docs/usage/README.md): Core usage patterns and service composition.
105
+ - [`docs/timezones/README.md`](docs/timezones/README.md): Timezone behavior, UTC defaults, and date-boundary guidance.
106
+ - [`docs/testing/README.md`](docs/testing/README.md): `FixedClock`, `MockClock`, and deterministic test patterns.
107
+
108
+ This [project's DeepWiki documentation](https://deepwiki.com/adriamontoto/clock-pattern) is also available for generated
109
+ repository navigation.
110
+
111
+ <p align="right">
112
+ <a href="#readme-top">🔼 Back to top</a>
113
+ </p><br><br>
114
+
115
+ <a name="quick-start"></a>
116
+
117
+ ## ⚡ Quick Start
118
+
119
+ Inject a `Clock` into code that needs the current time. Production code can pass a real clock, while tests can pass a
120
+ fixed or mock clock.
121
+
122
+ ```python
123
+ from clock_pattern import Clock, UtcClock
124
+
125
+
126
+ class TimestampService:
127
+ def __init__(self, *, clock: Clock) -> None:
128
+ self._clock = clock
129
+
130
+ def issued_at(self) -> str:
131
+ return self._clock.now().isoformat()
132
+
133
+
134
+ service = TimestampService(clock=UtcClock())
135
+ print(service.issued_at())
136
+ ```
137
+
138
+ Use [`SystemClock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/system_clock.py) when
139
+ you need a specific timezone:
140
+
141
+ ```python
142
+ from clock_pattern import SystemClock
143
+
144
+ clock = SystemClock(timezone='Europe/Madrid')
145
+ print(clock.now())
146
+ # >>> 2025-06-16 15:57:26.210964+02:00
147
+ ```
148
+
149
+ <p align="right">
150
+ <a href="#readme-top">🔼 Back to top</a>
151
+ </p><br><br>
152
+
153
+ <a name="why-inject-a-clock"></a>
154
+
155
+ ## 🧩 Why Inject a Clock?
156
+
157
+ Time is global state. Reading it directly from the operating system makes behavior depend on the moment a test happens
158
+ to run, the machine timezone, daylight-saving transitions, and the speed of the test suite.
159
+
160
+ Clock Pattern keeps those decisions explicit:
161
+
162
+ - Domain code depends on `Clock`, not on Python's global datetime functions.
163
+ - Tests can choose exact dates and datetimes without monkeypatching built-in modules.
164
+ - Production wiring decides whether the application uses UTC or another timezone.
165
+ - Custom clocks can be introduced for logical time, simulation, replay, or high-precision infrastructure.
166
+
167
+ The package exposes two methods:
168
+
169
+ | Method | Returns | Typical use |
170
+ | --- | --- | --- |
171
+ | `now()` | `datetime` | Timestamps, expiration windows, audit fields, elapsed-time calculations. |
172
+ | `today()` | `date` | Calendar rules, billing days, holiday checks, date-only decisions. |
173
+
174
+ <p align="right">
175
+ <a href="#readme-top">🔼 Back to top</a>
176
+ </p><br><br>
177
+
178
+ <a name="available-clocks"></a>
179
+
180
+ ## 📚 Available Clocks
181
+
182
+ The package offers several clock implementations to suit different needs:
183
+
184
+ | Clock | Import path | Purpose |
185
+ | --- | --- | --- |
186
+ | [`Clock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/models/clock.py) | `from clock_pattern import Clock` | Abstract contract for code that needs `now()` or `today()`. |
187
+ | [`SystemClock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/system_clock.py) | `from clock_pattern import SystemClock` | Production clock backed by system time in a configured timezone. |
188
+ | [`UtcClock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/utc_clock.py) | `from clock_pattern import UtcClock` | Production clock fixed to UTC. |
189
+ | `MonotonicClock` | `from clock_pattern import MonotonicClock` | Abstract contract for elapsed-time sources. |
190
+ | `SystemMonotonicClock` | `from clock_pattern import SystemMonotonicClock` | Production monotonic clock for elapsed-time measurement. |
191
+ | `SystemSleeper` / `SystemSleeperAsync` | `from clock_pattern import SystemSleeper, SystemSleeperAsync` | Injectable sync and async sleeping. |
192
+ | `Stopwatch` | `from clock_pattern import Stopwatch` | Measure elapsed seconds with `.start()`, `.end()`, or a context manager. |
193
+ | [`FixedClock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/testing/fixed_clock.py) | `from clock_pattern.clocks.testing import FixedClock` | Test clock that always returns the same datetime and derived date. |
194
+ | [`MockClock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/testing/mock_clock.py) | `from clock_pattern.clocks.testing import MockClock` | Test clock with prepared return values and call assertions. |
195
+
196
+ Use the top-level package for production clocks and `clock_pattern.clocks.testing` for test-only clocks.
197
+
198
+ <p align="right">
199
+ <a href="#readme-top">🔼 Back to top</a>
200
+ </p><br><br>
201
+
202
+ <a name="timezone-behavior"></a>
203
+
204
+ ## 🌍 Timezone Behavior
205
+
206
+ `SystemClock` accepts either an IANA timezone string or a `tzinfo` instance. It stores the timezone with `ZoneInfo` and
207
+ uses it for both `now()` and `today()`.
208
+
209
+ ```python
210
+ from datetime import UTC
211
+
212
+ from clock_pattern import SystemClock
213
+
214
+ utc_clock = SystemClock(timezone=UTC)
215
+ madrid_clock = SystemClock(timezone='Europe/Madrid')
216
+
217
+ print(utc_clock.timezone)
218
+ # >>> UTC
219
+ print(madrid_clock.timezone)
220
+ # >>> Europe/Madrid
221
+ ```
222
+
223
+ `UtcClock` is a convenience clock for the common production choice of UTC.
224
+
225
+ `today()` is calculated in the clock timezone. Around midnight, `SystemClock(timezone='UTC').today()` and
226
+ `SystemClock(timezone='America/New_York').today()` may return different dates. For more details, see
227
+ [`docs/timezones/README.md`](docs/timezones/README.md).
228
+
229
+ <p align="right">
230
+ <a href="#readme-top">🔼 Back to top</a>
231
+ </p><br><br>
232
+
233
+ <a name="testing-time-sensitive-code"></a>
234
+
235
+ ## 🧪 Testing Time-Sensitive Code
236
+
237
+ Use `FixedClock` when the test only needs a stable instant:
238
+
239
+ ```python
240
+ from datetime import datetime
241
+
242
+ from clock_pattern.clocks.testing import FixedClock
243
+
244
+ clock = FixedClock(instant=datetime(year=2025, month=1, day=1, hour=10, minute=30))
245
+
246
+ assert clock.now().isoformat() == '2025-01-01T10:30:00+00:00'
247
+ assert clock.today().isoformat() == '2025-01-01'
248
+ ```
249
+
250
+ Use `MockClock` when the test also needs to prove that time was requested:
251
+
252
+ ```python
253
+ from datetime import date
254
+
255
+ from clock_pattern.clocks.testing import MockClock
256
+
257
+ clock = MockClock()
258
+ clock.prepare_today_method_return_value(today=date(year=2025, month=1, day=7))
259
+
260
+ assert clock.today() == date(year=2025, month=1, day=7)
261
+ clock.assert_today_method_was_called_once()
262
+ clock.assert_now_method_was_not_called()
263
+ ```
264
+
265
+ More testing recipes are available in [`docs/testing/README.md`](docs/testing/README.md).
266
+
267
+ <p align="right">
268
+ <a href="#readme-top">🔼 Back to top</a>
269
+ </p><br><br>
270
+
271
+ <a name="real-life-case-christmas-detector-service"></a>
272
+
273
+ ## 🎄 Real-Life Case: Christmas Detector Service
274
+
275
+ This service checks whether the current date falls within a Christmas holiday range. The service depends on `Clock`, so
276
+ production code can use [`UtcClock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/utc_clock.py)
277
+ and tests can use [`MockClock`](https://github.com/adriamontoto/clock-pattern/blob/master/clock_pattern/clocks/testing/mock_clock.py)
278
+ without changing the service.
279
+
280
+ ```python
281
+ from datetime import date
282
+
283
+ from clock_pattern import Clock, UtcClock
284
+ from clock_pattern.clocks.testing import MockClock
285
+
286
+
287
+ class ChristmasDetectorService:
288
+ def __init__(self, *, clock: Clock) -> None:
289
+ self._clock = clock
290
+ self._christmas_start = date(year=2024, month=12, day=24)
291
+ self._christmas_end = date(year=2025, month=1, day=6)
292
+
293
+ def is_christmas(self) -> bool:
294
+ return self._christmas_start <= self._clock.today() <= self._christmas_end
295
+
296
+
297
+ clock = UtcClock()
298
+ service = ChristmasDetectorService(clock=clock)
299
+
300
+ print(service.is_christmas())
301
+ # >>> False
302
+
303
+
304
+ def test_christmas_detector_is_christmas() -> None:
305
+ clock = MockClock()
306
+ service = ChristmasDetectorService(clock=clock)
307
+
308
+ today = date(year=2024, month=12, day=25)
309
+ clock.prepare_today_method_return_value(today=today)
310
+
311
+ assert service.is_christmas() is True
312
+ clock.assert_today_method_was_called_once()
313
+
314
+
315
+ def test_christmas_detector_is_not_christmas() -> None:
316
+ clock = MockClock()
317
+ service = ChristmasDetectorService(clock=clock)
318
+
319
+ today = date(year=2025, month=1, day=7)
320
+ clock.prepare_today_method_return_value(today=today)
321
+
322
+ assert service.is_christmas() is False
323
+ clock.assert_today_method_was_called_once()
324
+ ```
325
+
326
+ <p align="right">
327
+ <a href="#readme-top">🔼 Back to top</a>
328
+ </p><br><br>
329
+
330
+ <a name="contributing"></a>
331
+
332
+ ## 🤝 Contributing
333
+
334
+ We love community help! Before you open an issue or pull request, please read:
335
+
336
+ - [`🤝 How to Contribute`](https://github.com/adriamontoto/clock-pattern/blob/master/.github/CONTRIBUTING.md)
337
+ - [`🧭 Code of Conduct`](https://github.com/adriamontoto/clock-pattern/blob/master/.github/CODE_OF_CONDUCT.md)
338
+ - [`🔐 Security Policy`](https://github.com/adriamontoto/clock-pattern/blob/master/.github/SECURITY.md)
339
+
340
+ _Thank you for helping make **🕰️ Clock Pattern** package awesome! 🌟_
341
+
342
+ <p align="right">
343
+ <a href="#readme-top">🔼 Back to top</a>
344
+ </p><br><br>
345
+
346
+ <a name="license"></a>
347
+
348
+ ## 🔑 License
349
+
350
+ This project is licensed under the terms of the [`MIT license`](https://github.com/adriamontoto/clock-pattern/blob/master/LICENSE.md).
351
+
352
+ <p align="right">
353
+ <a href="#readme-top">🔼 Back to top</a>
354
+ </p>