nanowatch 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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,313 @@
1
+ Metadata-Version: 2.4
2
+ Name: nanowatch
3
+ Version: 0.1.0
4
+ Summary: High-precision Python performance measurement toolkit
5
+ License: MIT
6
+ Keywords: performance,profiling,timing,benchmark,measurement
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Software Development
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE.md
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-asyncio; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # nanowatch
26
+
27
+ High-precision Python performance measurement toolkit.
28
+ Nanosecond accuracy. Zero dependencies. Minimal output.
29
+
30
+ ---
31
+
32
+ ## Install
33
+
34
+ ```
35
+ pip install nanowatch
36
+ ```
37
+
38
+ Or from source:
39
+
40
+ ```
41
+ pip install -e .
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Interfaces
47
+
48
+ ### 1. Function Decorator
49
+
50
+ ```python
51
+ from nanowatch import watch
52
+
53
+ @watch
54
+ def compute(n):
55
+ return sum(range(n))
56
+
57
+ # With a custom label
58
+ @watch("heavy computation")
59
+ def compute(n):
60
+ return sum(range(n))
61
+
62
+ # Async functions work identically
63
+ @watch
64
+ async def fetch_data(url):
65
+ ...
66
+ ```
67
+
68
+ **Output:**
69
+ ```
70
+ compute 1.243 ms
71
+ ```
72
+
73
+ ---
74
+
75
+ ### 2. Code Block Context Manager
76
+
77
+ ```python
78
+ from nanowatch import watch_block
79
+
80
+ with watch_block("db query"):
81
+ results = db.execute(sql)
82
+ ```
83
+
84
+ ---
85
+
86
+ ### 3. Inline Call (no decorator needed)
87
+
88
+ ```python
89
+ from nanowatch import watch_call
90
+
91
+ data = watch_call(json.loads, raw_string, name="parse response")
92
+ ```
93
+
94
+ ---
95
+
96
+ ### 4. Class Mixin (auto-instruments all public methods)
97
+
98
+ ```python
99
+ from nanowatch import WatchedMixin
100
+
101
+ class UserService(WatchedMixin):
102
+ _watch_prefix = "UserService" # optional, defaults to class name
103
+
104
+ def fetch_user(self, user_id):
105
+ ...
106
+
107
+ def save_user(self, user):
108
+ ...
109
+
110
+ def _internal_helper(self): # skipped (underscore prefix)
111
+ ...
112
+ ```
113
+
114
+ Every call to `fetch_user` or `save_user` is automatically timed.
115
+
116
+ **Custom collector via DI:**
117
+ ```python
118
+ from nanowatch import WatchedMixin, Collector
119
+
120
+ my_collector = Collector()
121
+
122
+ class OrderService(WatchedMixin):
123
+ _watch_collector = my_collector
124
+ _watch_prefix = "OrderService"
125
+
126
+ def create_order(self, data):
127
+ ...
128
+ ```
129
+
130
+ ---
131
+
132
+ ### 5. WSGI Middleware (Flask, Django, etc.)
133
+
134
+ ```python
135
+ from nanowatch import WsgiMiddleware
136
+
137
+ # Flask
138
+ app.wsgi_app = WsgiMiddleware(app.wsgi_app)
139
+
140
+ # Django (in wsgi.py)
141
+ application = WsgiMiddleware(get_wsgi_application())
142
+ ```
143
+
144
+ **Output per request:**
145
+ ```
146
+ HTTP GET /api/users 4.231 ms [method=GET, path=/api/users]
147
+ ```
148
+
149
+ ---
150
+
151
+ ### 6. ASGI Middleware (FastAPI, Starlette, etc.)
152
+
153
+ ```python
154
+ from nanowatch import AsgiMiddleware
155
+
156
+ # FastAPI
157
+ app.add_middleware(AsgiMiddleware)
158
+
159
+ # Or manually wrap
160
+ app = AsgiMiddleware(app)
161
+ ```
162
+
163
+ ---
164
+
165
+ ### 7. Line Profiler (checkpoint-based)
166
+
167
+ Measures time between named points inside a function.
168
+
169
+ ```python
170
+ from nanowatch import LineProfiler
171
+
172
+ def process_order(order):
173
+ prof = LineProfiler("process_order")
174
+
175
+ validate(order)
176
+ prof.mark("validated")
177
+
178
+ result = db.save(order)
179
+ prof.mark("saved to db")
180
+
181
+ notify(order)
182
+ prof.mark("notified")
183
+
184
+ prof.finish()
185
+ ```
186
+
187
+ **Output:**
188
+ ```
189
+ process_order | validated 312 us [session=process_order, checkpoint=validated]
190
+ process_order | saved to db 2.841 ms [session=process_order, checkpoint=saved to db]
191
+ process_order | notified 1.102 ms [session=process_order, checkpoint=notified]
192
+ [nanowatch] session 'process_order' complete (3 checkpoints)
193
+ ------------------------------------------------------------------------
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Reports
199
+
200
+ ### Print summary to stdout
201
+
202
+ ```python
203
+ import nanowatch
204
+
205
+ # ... run your code ...
206
+
207
+ nanowatch.summary()
208
+ ```
209
+
210
+ **Output:**
211
+ ```
212
+ ========================================================================
213
+ nanowatch | Performance Summary
214
+ 2025-06-01 14:32:10
215
+ ========================================================================
216
+ UserService.fetch_user
217
+ calls : 48
218
+ min : 812 us
219
+ max : 4.231 ms
220
+ avg : 1.103 ms
221
+ total : 52.944 ms
222
+ ------------------------------------------------------------------------
223
+ HTTP GET /api/users 4.231 ms [method=GET, path=/api/users]
224
+ ------------------------------------------------------------------------
225
+ Total tracked time : 1.204 s
226
+ Total measurements : 57
227
+ ========================================================================
228
+ ```
229
+
230
+ ### Save to JSON file
231
+
232
+ ```python
233
+ nanowatch.save("perf_results.json")
234
+ ```
235
+
236
+ ```json
237
+ {
238
+ "generated_at": "2025-06-01T14:32:10.123456",
239
+ "total_measurements": 57,
240
+ "records": [
241
+ {
242
+ "name": "UserService.fetch_user",
243
+ "duration_ns": 1103000,
244
+ "duration_us": 1103.0,
245
+ "duration_ms": 1.103,
246
+ "duration_s": 0.001103,
247
+ "context": {}
248
+ }
249
+ ],
250
+ "groups": {
251
+ "UserService.fetch_user": {
252
+ "count": 48,
253
+ "min_ns": 812000,
254
+ "max_ns": 4231000,
255
+ "avg_ns": 1103000,
256
+ "total_ns": 52944000
257
+ }
258
+ }
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Custom Collector (Isolation / Testing)
265
+
266
+ All interfaces accept an optional `collector` parameter for DI:
267
+
268
+ ```python
269
+ from nanowatch import watch, Collector
270
+
271
+ test_collector = Collector()
272
+
273
+ @watch(collector=test_collector)
274
+ def my_fn():
275
+ ...
276
+
277
+ my_fn()
278
+ print(test_collector.stats("my_fn"))
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Reset
284
+
285
+ ```python
286
+ nanowatch.reset() # clears the global collector
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Precision
292
+
293
+ All measurements use `time.perf_counter_ns`, Python's highest-resolution
294
+ monotonic clock. Results are stored as raw integers (nanoseconds) and
295
+ converted only for display.
296
+
297
+ ---
298
+
299
+ ## Project Structure
300
+
301
+ ```
302
+ src/nanowatch/
303
+ core/
304
+ timer.py # Timer, TimingRecord
305
+ collector.py # Collector, default_collector
306
+ interfaces/
307
+ decorators.py # @watch, watch_block, watch_call
308
+ mixin.py # WatchedMixin
309
+ middleware.py # WsgiMiddleware, AsgiMiddleware
310
+ line_profiler.py # LineProfiler
311
+ output/
312
+ formatter.py # console + file output
313
+ ```
@@ -0,0 +1,289 @@
1
+ # nanowatch
2
+
3
+ High-precision Python performance measurement toolkit.
4
+ Nanosecond accuracy. Zero dependencies. Minimal output.
5
+
6
+ ---
7
+
8
+ ## Install
9
+
10
+ ```
11
+ pip install nanowatch
12
+ ```
13
+
14
+ Or from source:
15
+
16
+ ```
17
+ pip install -e .
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Interfaces
23
+
24
+ ### 1. Function Decorator
25
+
26
+ ```python
27
+ from nanowatch import watch
28
+
29
+ @watch
30
+ def compute(n):
31
+ return sum(range(n))
32
+
33
+ # With a custom label
34
+ @watch("heavy computation")
35
+ def compute(n):
36
+ return sum(range(n))
37
+
38
+ # Async functions work identically
39
+ @watch
40
+ async def fetch_data(url):
41
+ ...
42
+ ```
43
+
44
+ **Output:**
45
+ ```
46
+ compute 1.243 ms
47
+ ```
48
+
49
+ ---
50
+
51
+ ### 2. Code Block Context Manager
52
+
53
+ ```python
54
+ from nanowatch import watch_block
55
+
56
+ with watch_block("db query"):
57
+ results = db.execute(sql)
58
+ ```
59
+
60
+ ---
61
+
62
+ ### 3. Inline Call (no decorator needed)
63
+
64
+ ```python
65
+ from nanowatch import watch_call
66
+
67
+ data = watch_call(json.loads, raw_string, name="parse response")
68
+ ```
69
+
70
+ ---
71
+
72
+ ### 4. Class Mixin (auto-instruments all public methods)
73
+
74
+ ```python
75
+ from nanowatch import WatchedMixin
76
+
77
+ class UserService(WatchedMixin):
78
+ _watch_prefix = "UserService" # optional, defaults to class name
79
+
80
+ def fetch_user(self, user_id):
81
+ ...
82
+
83
+ def save_user(self, user):
84
+ ...
85
+
86
+ def _internal_helper(self): # skipped (underscore prefix)
87
+ ...
88
+ ```
89
+
90
+ Every call to `fetch_user` or `save_user` is automatically timed.
91
+
92
+ **Custom collector via DI:**
93
+ ```python
94
+ from nanowatch import WatchedMixin, Collector
95
+
96
+ my_collector = Collector()
97
+
98
+ class OrderService(WatchedMixin):
99
+ _watch_collector = my_collector
100
+ _watch_prefix = "OrderService"
101
+
102
+ def create_order(self, data):
103
+ ...
104
+ ```
105
+
106
+ ---
107
+
108
+ ### 5. WSGI Middleware (Flask, Django, etc.)
109
+
110
+ ```python
111
+ from nanowatch import WsgiMiddleware
112
+
113
+ # Flask
114
+ app.wsgi_app = WsgiMiddleware(app.wsgi_app)
115
+
116
+ # Django (in wsgi.py)
117
+ application = WsgiMiddleware(get_wsgi_application())
118
+ ```
119
+
120
+ **Output per request:**
121
+ ```
122
+ HTTP GET /api/users 4.231 ms [method=GET, path=/api/users]
123
+ ```
124
+
125
+ ---
126
+
127
+ ### 6. ASGI Middleware (FastAPI, Starlette, etc.)
128
+
129
+ ```python
130
+ from nanowatch import AsgiMiddleware
131
+
132
+ # FastAPI
133
+ app.add_middleware(AsgiMiddleware)
134
+
135
+ # Or manually wrap
136
+ app = AsgiMiddleware(app)
137
+ ```
138
+
139
+ ---
140
+
141
+ ### 7. Line Profiler (checkpoint-based)
142
+
143
+ Measures time between named points inside a function.
144
+
145
+ ```python
146
+ from nanowatch import LineProfiler
147
+
148
+ def process_order(order):
149
+ prof = LineProfiler("process_order")
150
+
151
+ validate(order)
152
+ prof.mark("validated")
153
+
154
+ result = db.save(order)
155
+ prof.mark("saved to db")
156
+
157
+ notify(order)
158
+ prof.mark("notified")
159
+
160
+ prof.finish()
161
+ ```
162
+
163
+ **Output:**
164
+ ```
165
+ process_order | validated 312 us [session=process_order, checkpoint=validated]
166
+ process_order | saved to db 2.841 ms [session=process_order, checkpoint=saved to db]
167
+ process_order | notified 1.102 ms [session=process_order, checkpoint=notified]
168
+ [nanowatch] session 'process_order' complete (3 checkpoints)
169
+ ------------------------------------------------------------------------
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Reports
175
+
176
+ ### Print summary to stdout
177
+
178
+ ```python
179
+ import nanowatch
180
+
181
+ # ... run your code ...
182
+
183
+ nanowatch.summary()
184
+ ```
185
+
186
+ **Output:**
187
+ ```
188
+ ========================================================================
189
+ nanowatch | Performance Summary
190
+ 2025-06-01 14:32:10
191
+ ========================================================================
192
+ UserService.fetch_user
193
+ calls : 48
194
+ min : 812 us
195
+ max : 4.231 ms
196
+ avg : 1.103 ms
197
+ total : 52.944 ms
198
+ ------------------------------------------------------------------------
199
+ HTTP GET /api/users 4.231 ms [method=GET, path=/api/users]
200
+ ------------------------------------------------------------------------
201
+ Total tracked time : 1.204 s
202
+ Total measurements : 57
203
+ ========================================================================
204
+ ```
205
+
206
+ ### Save to JSON file
207
+
208
+ ```python
209
+ nanowatch.save("perf_results.json")
210
+ ```
211
+
212
+ ```json
213
+ {
214
+ "generated_at": "2025-06-01T14:32:10.123456",
215
+ "total_measurements": 57,
216
+ "records": [
217
+ {
218
+ "name": "UserService.fetch_user",
219
+ "duration_ns": 1103000,
220
+ "duration_us": 1103.0,
221
+ "duration_ms": 1.103,
222
+ "duration_s": 0.001103,
223
+ "context": {}
224
+ }
225
+ ],
226
+ "groups": {
227
+ "UserService.fetch_user": {
228
+ "count": 48,
229
+ "min_ns": 812000,
230
+ "max_ns": 4231000,
231
+ "avg_ns": 1103000,
232
+ "total_ns": 52944000
233
+ }
234
+ }
235
+ }
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Custom Collector (Isolation / Testing)
241
+
242
+ All interfaces accept an optional `collector` parameter for DI:
243
+
244
+ ```python
245
+ from nanowatch import watch, Collector
246
+
247
+ test_collector = Collector()
248
+
249
+ @watch(collector=test_collector)
250
+ def my_fn():
251
+ ...
252
+
253
+ my_fn()
254
+ print(test_collector.stats("my_fn"))
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Reset
260
+
261
+ ```python
262
+ nanowatch.reset() # clears the global collector
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Precision
268
+
269
+ All measurements use `time.perf_counter_ns`, Python's highest-resolution
270
+ monotonic clock. Results are stored as raw integers (nanoseconds) and
271
+ converted only for display.
272
+
273
+ ---
274
+
275
+ ## Project Structure
276
+
277
+ ```
278
+ src/nanowatch/
279
+ core/
280
+ timer.py # Timer, TimingRecord
281
+ collector.py # Collector, default_collector
282
+ interfaces/
283
+ decorators.py # @watch, watch_block, watch_call
284
+ mixin.py # WatchedMixin
285
+ middleware.py # WsgiMiddleware, AsgiMiddleware
286
+ line_profiler.py # LineProfiler
287
+ output/
288
+ formatter.py # console + file output
289
+ ```
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nanowatch"
7
+ version = "0.1.0"
8
+ description = "High-precision Python performance measurement toolkit"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ keywords = ["performance", "profiling", "timing", "benchmark", "measurement"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Software Development",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ "Topic :: Utilities",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pytest", "pytest-asyncio"]
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
31
+
32
+ [tool.setuptools.package-dir]
33
+ "" = "src"
34
+
35
+ [tool.pytest.ini_options]
36
+ asyncio_mode = "auto"
37
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+