with-line-profiler 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dominik Köhler
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,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: with-line-profiler
3
+ Version: 0.1.0
4
+ Summary: Context-manager based line-by-line profiler for Python functions
5
+ Project-URL: Homepage, https://github.com/mathematiger/lineprofiler
6
+ Project-URL: Repository, https://github.com/mathematiger/lineprofiler
7
+ Project-URL: Issues, https://github.com/mathematiger/lineprofiler/issues
8
+ Author-email: mathematiger <mcop.dkoehler@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: debugging,performance,profiler,profiling,timing
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Debuggers
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: typing-extensions>=4.0.0
25
+ Description-Content-Type: text/markdown
26
+
27
+ # lineprofiler
28
+ Statistical profiler to find lines that take a long time to compute. One can specify a folder, wherein the profiler traces lines.
29
+ The profiler can be bound using `with`.
30
+
31
+ ## Features
32
+ - **Zero configuration** – just wrap code in a `with` block
33
+ - **Line-level timing** – see exactly which lines are slow
34
+ - **Auto-filtering** – only profiles code in your project (auto-detects git repo root)
35
+ - **Flexible output** – sort by time, hits, or line number; filter by threshold
36
+
37
+ ## Installation
38
+ `pip install lineprofiler`
39
+
40
+ ## Workflow
41
+ ```python
42
+ from lineprofiler import LineProfiler
43
+ profiler = LineProfiler(project_folder="path/to/your/project")
44
+ profiler.clear()
45
+ with profiler:
46
+ your_function()
47
+ profiler.print_global_top_stats(min_time_us=0.01, top_n=40)
48
+ ```
49
+
50
+ | Method | Description |
51
+ |--------|-------------|
52
+ | `print_stats(min_time_us, top_n_lines, sort_by)` | Print per-function statistics |
53
+ | `print_global_top_stats(top_n, min_time_us, sort_by)` | Print top N lines across all functions |
54
+ | `get_stats()` | Get raw `FunctionStats` dictionary |
55
+ | `clear()` / `reset()` | Clear all collected data |
56
+
57
+ ## Licence
58
+ MIT
@@ -0,0 +1,32 @@
1
+ # lineprofiler
2
+ Statistical profiler to find lines that take a long time to compute. One can specify a folder, wherein the profiler traces lines.
3
+ The profiler can be bound using `with`.
4
+
5
+ ## Features
6
+ - **Zero configuration** – just wrap code in a `with` block
7
+ - **Line-level timing** – see exactly which lines are slow
8
+ - **Auto-filtering** – only profiles code in your project (auto-detects git repo root)
9
+ - **Flexible output** – sort by time, hits, or line number; filter by threshold
10
+
11
+ ## Installation
12
+ `pip install lineprofiler`
13
+
14
+ ## Workflow
15
+ ```python
16
+ from lineprofiler import LineProfiler
17
+ profiler = LineProfiler(project_folder="path/to/your/project")
18
+ profiler.clear()
19
+ with profiler:
20
+ your_function()
21
+ profiler.print_global_top_stats(min_time_us=0.01, top_n=40)
22
+ ```
23
+
24
+ | Method | Description |
25
+ |--------|-------------|
26
+ | `print_stats(min_time_us, top_n_lines, sort_by)` | Print per-function statistics |
27
+ | `print_global_top_stats(top_n, min_time_us, sort_by)` | Print top N lines across all functions |
28
+ | `get_stats()` | Get raw `FunctionStats` dictionary |
29
+ | `clear()` / `reset()` | Clear all collected data |
30
+
31
+ ## Licence
32
+ MIT
File without changes
@@ -0,0 +1,523 @@
1
+ """Context-manager based line-by-line profiler for Python functions.
2
+
3
+ This module provides a simple context manager interface for profiling code blocks
4
+ and functions with detailed line-by-line timing information.
5
+
6
+ Test components:
7
+ - Context manager protocol (__enter__/__exit__)
8
+ - Accurate timing measurements with sys.settrace
9
+ - Proper trace function cleanup and restoration
10
+ - Thread-safe profiler state management
11
+ - Correct line timing calculations
12
+ """
13
+ import inspect
14
+ import sys
15
+ import time
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from types import FrameType, TracebackType
19
+
20
+ from typing_extensions import Self
21
+
22
+
23
+ @dataclass
24
+ class LineStats:
25
+ """Statistics for a single line of code.
26
+
27
+ Test components:
28
+ - Correct accumulation of hits and total_time
29
+ - Accurate average_time calculation
30
+ - Proper handling of zero hits
31
+ """
32
+
33
+ line_number: int
34
+ hits: int = 0
35
+ total_time: float = 0.0
36
+
37
+ @property
38
+ def average_time(self) -> float:
39
+ """Calculate average time per execution.
40
+
41
+ Returns
42
+ -------
43
+ Average time in seconds, or 0.0 if no hits
44
+
45
+ Test components:
46
+ - Division by zero handling
47
+ - Accurate time averaging
48
+ """
49
+ return self.total_time / self.hits if self.hits > 0 else 0.0
50
+
51
+
52
+ @dataclass
53
+ class FunctionStats:
54
+ """Statistics for an entire function.
55
+
56
+ Test components:
57
+ - Correct line_stats dictionary management
58
+ - Accurate source code storage
59
+ - Proper total_time accumulation
60
+ """
61
+
62
+ filename: str
63
+ function_name: str
64
+ first_line: int
65
+ line_stats: dict[int, LineStats] = field(default_factory=dict)
66
+ source_lines: dict[int, str] = field(default_factory=dict)
67
+ total_time: float = 0.0
68
+
69
+
70
+ class LineProfiler:
71
+ """Context manager for line-by-line profiling of code blocks.
72
+
73
+ Usage:
74
+ profiler = LineProfiler()
75
+ with profiler:
76
+ # Code to profile
77
+ result = some_function()
78
+
79
+ profiler.print_stats()
80
+
81
+ Test components:
82
+ - Context manager __enter__ and __exit__ implementation
83
+ - Correct trace function registration and cleanup
84
+ - Accurate timing of line executions
85
+ - Proper handling of nested function calls
86
+ - Thread-safe state management
87
+ - File and line number tracking
88
+ """
89
+
90
+ def __init__(self, project_folder: str | Path | None = None) -> None:
91
+ """Initialize the profiler.
92
+
93
+ Args:
94
+ project_folder: Optional folder path to filter results (e.g., "pandapower_env")
95
+
96
+ Test components:
97
+ - Proper initialization of all tracking dictionaries
98
+ - Correct default state setup
99
+ - Path resolution for project_folder
100
+ """
101
+ self._function_stats: dict[tuple[str, str, int], FunctionStats] = {}
102
+ self._enabled: bool = False
103
+ self._last_time: float = 0.0
104
+ self._last_line: int | None = None
105
+ self._current_function_key: tuple[str, str, int] | None = None
106
+ self._old_trace = sys.gettrace()
107
+
108
+ # Store project folder for filtering
109
+ if project_folder is not None:
110
+ self._project_folder = Path(project_folder).resolve()
111
+ else:
112
+ # Auto-detect vom Caller
113
+
114
+ caller_frame = inspect.currentframe()
115
+ if caller_frame and caller_frame.f_back:
116
+ caller_file = caller_frame.f_back.f_code.co_filename
117
+ self._project_folder = self._find_repo_root(caller_file)
118
+
119
+ def __enter__(self) -> Self:
120
+ """Enable profiling when entering context.
121
+
122
+ Returns
123
+ -------
124
+ Self for context manager protocol
125
+
126
+ Test components:
127
+ - Correct storage of previous trace function
128
+ - Proper settrace registration
129
+ - Accurate initial timestamp
130
+ - State flag updates
131
+ """
132
+ self._enabled = True
133
+ self._old_trace = sys.gettrace()
134
+ sys.settrace(self._trace_callback)
135
+ self._last_time = time.perf_counter()
136
+ return self
137
+
138
+ def __exit__(
139
+ self,
140
+ exc_type: type[BaseException] | None,
141
+ exc_val: BaseException | None,
142
+ exc_tb: TracebackType | None,
143
+ ) -> None:
144
+ """Disable profiling when exiting context.
145
+
146
+ Args:
147
+ exc_type: Exception type if raised
148
+ exc_val: Exception value if raised
149
+ exc_tb: Exception traceback if raised
150
+
151
+ Test components:
152
+ - Proper trace function restoration
153
+ - State cleanup on exit
154
+ - Correct handling of exceptions during profiling
155
+ - No interference with exception propagation
156
+ """
157
+ self._enabled = False
158
+ sys.settrace(self._old_trace)
159
+
160
+ def _trace_callback( # noqa: ANN202, C901
161
+ self,
162
+ frame: FrameType,
163
+ event: str,
164
+ arg, # noqa: ANN001, ARG002; needed for compliance
165
+ ):
166
+ """Trace function called by Python interpreter for each line.
167
+
168
+ Args:
169
+ frame: Current execution frame
170
+ event: Event type ('call', 'line', 'return', etc.)
171
+ arg: Event-specific argument
172
+
173
+ Returns
174
+ -------
175
+ Self to continue tracing, or None to stop
176
+
177
+ Test components:
178
+ - Correct event type handling ('call', 'line', 'return')
179
+ - Accurate time delta calculations
180
+ - Proper frame inspection (filename, function name, line number)
181
+ - FunctionStats creation and updates
182
+ - Source code extraction and caching
183
+ """
184
+ if not self._enabled:
185
+ return None
186
+
187
+ current_time = time.perf_counter()
188
+
189
+ if event == "call":
190
+ # New function called
191
+ filename = frame.f_code.co_filename
192
+ if not self._is_in_project_folder(filename):
193
+ return None
194
+ function_name = frame.f_code.co_name
195
+ first_line = frame.f_code.co_firstlineno
196
+
197
+ key = (filename, function_name, first_line)
198
+
199
+ if key not in self._function_stats:
200
+ self._function_stats[key] = FunctionStats(
201
+ filename=filename,
202
+ function_name=function_name,
203
+ first_line=first_line,
204
+ )
205
+ # Load source lines
206
+ self._load_source_lines(key)
207
+
208
+ self._current_function_key = key
209
+ self._last_time = current_time
210
+ self._last_line = None
211
+
212
+ elif event == "line":
213
+ # Line executed
214
+ if self._current_function_key is not None and self._last_line is not None:
215
+ time_delta = current_time - self._last_time
216
+
217
+ func_stats = self._function_stats[self._current_function_key]
218
+
219
+ if self._last_line not in func_stats.line_stats:
220
+ func_stats.line_stats[self._last_line] = LineStats(
221
+ line_number=self._last_line,
222
+ )
223
+
224
+ line_stats = func_stats.line_stats[self._last_line]
225
+ line_stats.hits += 1
226
+ line_stats.total_time += time_delta
227
+ func_stats.total_time += time_delta
228
+
229
+ self._last_line = frame.f_lineno
230
+ self._last_time = current_time
231
+
232
+ elif event == "return":
233
+ # Function returning
234
+ if self._current_function_key is not None and self._last_line is not None:
235
+ time_delta = current_time - self._last_time
236
+
237
+ func_stats = self._function_stats[self._current_function_key]
238
+
239
+ if self._last_line not in func_stats.line_stats:
240
+ func_stats.line_stats[self._last_line] = LineStats(
241
+ line_number=self._last_line,
242
+ )
243
+
244
+ line_stats = func_stats.line_stats[self._last_line]
245
+ line_stats.hits += 1
246
+ line_stats.total_time += time_delta
247
+ func_stats.total_time += time_delta
248
+
249
+ self._current_function_key = None
250
+ self._last_line = None
251
+
252
+ return self._trace_callback
253
+
254
+ def _load_source_lines(self, key: tuple[str, str, int]) -> None:
255
+ """Load source code lines for a function.
256
+
257
+ Args:
258
+ key: Tuple of (filename, function_name, first_line)
259
+
260
+ Test components:
261
+ - Correct file reading and line extraction
262
+ - Proper handling of missing files
263
+ - UTF-8 encoding support
264
+ - Line number indexing
265
+ """
266
+ filename, _, _ = key
267
+ func_stats = self._function_stats[key]
268
+
269
+ try:
270
+ path = Path(filename)
271
+ if path.exists():
272
+ with Path.open(path, encoding="utf-8") as f:
273
+ lines = f.readlines()
274
+ for i, line in enumerate(lines, start=1):
275
+ func_stats.source_lines[i] = line.rstrip()
276
+ except (OSError, UnicodeDecodeError):
277
+ # If we can't read the file, just continue
278
+ pass
279
+
280
+ def _find_repo_root(self, start_path: str) -> Path:
281
+ """Return the git repo root (directory containing .git)."""
282
+ p = Path(start_path).resolve()
283
+
284
+ for parent in [p] + list(p.parents): # noqa: RUF005
285
+ if (parent / ".git").exists():
286
+ return parent
287
+
288
+ # No git repo found → fallback to start_path
289
+ return p
290
+
291
+ def _is_in_project_folder(self, filename: str) -> bool:
292
+ if self._project_folder is None:
293
+ return True # type: ignore[unreachable]
294
+
295
+ try:
296
+ file_path = Path(filename).resolve()
297
+ # Prüfe ob file_path relativ zu project_folder ist
298
+ try:
299
+ file_path.relative_to(self._project_folder)
300
+ except ValueError:
301
+ return False
302
+ else:
303
+ return True
304
+ except (OSError, ValueError):
305
+ return False
306
+
307
+
308
+ def print_stats( # noqa: C901
309
+ self,
310
+ min_time_us: float = 0.0,
311
+ top_n_lines: int | None = None,
312
+ sort_by: str = "time",
313
+ ) -> None:
314
+ """Print detailed profiling statistics per function.
315
+
316
+ Args:
317
+ min_time_us: Minimum time in microseconds to display a line
318
+ top_n_lines: If set, only show the top N lines per function
319
+ sort_by: How to sort lines - "time" (total time), "hits" (call count),
320
+ or "line" (line number). Default is "time".
321
+
322
+ Test components:
323
+ - Correct formatting of time values (seconds to microseconds)
324
+ - Proper sorting by line number, time, or hits
325
+ - Accurate percentage calculations
326
+ - Column alignment and formatting
327
+ - Filtering based on min_time_us threshold
328
+ - Correct limiting to top_n_lines
329
+ - Project folder filtering
330
+ """
331
+ if not self._function_stats:
332
+ print("No profiling data collected.")# noqa: T201
333
+ return
334
+
335
+ for key, func_stats in sorted(self._function_stats.items()):
336
+ filename, function_name, first_line = key
337
+
338
+ # Filter by project folder if set
339
+ if not self._is_in_project_folder(filename):
340
+ print(f"filename not in folde: {filename}")# noqa: T201
341
+ continue
342
+
343
+ if not func_stats.line_stats:
344
+ continue
345
+
346
+ print("=" * 100)# noqa: T201
347
+ print(f"File: {filename}")# noqa: T201
348
+ print(f"Function: {function_name} at line {first_line}")# noqa: T201
349
+ print(f"Total time: {func_stats.total_time * 1e6:.1f} µs")# noqa: T201
350
+ print("=" * 100) # noqa: T201
351
+ print(f"{'Line #':<8} {'Hits':<10} {'Time (µs)':<15} {'Per Hit (µs)':<15} {'% Time':<10} {'Line Content'}") # noqa: T201
352
+ print("-" * 100) # noqa: T201
353
+
354
+ # Collect all lines with stats
355
+ line_data: list[tuple[int, LineStats]] = []
356
+ for line_num in func_stats.line_stats:
357
+ line_stats = func_stats.line_stats[line_num]
358
+ time_us = line_stats.total_time * 1e6
359
+
360
+ if time_us >= min_time_us:
361
+ line_data.append((line_num, line_stats))
362
+
363
+ # Sort based on sort_by parameter
364
+ if sort_by == "time":
365
+ line_data.sort(key=lambda x: x[1].total_time, reverse=True)
366
+ elif sort_by == "hits":
367
+ line_data.sort(key=lambda x: x[1].hits, reverse=True)
368
+ else: # sort_by == "line"
369
+ line_data.sort(key=lambda x: x[0])
370
+
371
+ # Limit to top N if requested
372
+ if top_n_lines is not None:
373
+ line_data = line_data[:top_n_lines]
374
+
375
+ # Print the lines
376
+ for line_num, line_stats in line_data:
377
+ time_us = line_stats.total_time * 1e6
378
+ avg_time_us = line_stats.average_time * 1e6
379
+ percent = (line_stats.total_time / func_stats.total_time * 100
380
+ if func_stats.total_time > 0 else 0.0)
381
+
382
+ source_line = func_stats.source_lines.get(line_num, "")
383
+ # Truncate long lines
384
+ if len(source_line) > 50: # noqa: PLR2004
385
+ source_line = source_line[:47] + "..."
386
+
387
+ print(f"{line_num:<8} {line_stats.hits:<10} {time_us:<15.1f} " # noqa: T201
388
+ f"{avg_time_us:<15.1f} {percent:<10.1f} {source_line}")
389
+
390
+ print() # noqa: T201
391
+
392
+ def print_global_top_stats( # noqa: C901, PLR0912
393
+ self,
394
+ top_n: int = 10,
395
+ min_time_us: float = 0.0,
396
+ sort_by: str = "time",
397
+ ) -> None:
398
+ """Print a global summary of the top lines across all functions.
399
+
400
+ Args:
401
+ top_n: Number of top lines to display
402
+ min_time_us: Minimum time in microseconds to include a line
403
+ sort_by: How to sort - "time" (total time) or "hits" (call count)
404
+
405
+ Test components:
406
+ - Correct aggregation across all functions
407
+ - Proper sorting by time or hits
408
+ - Project folder filtering
409
+ - Accurate time and percentage calculations
410
+ - Proper table formatting
411
+ """
412
+ all_lines: list[dict] = []
413
+
414
+ for key, func_stats in self._function_stats.items():
415
+ filename, function_name, first_line = key
416
+
417
+ # Filter by project folder if set
418
+ if not self._is_in_project_folder(filename):
419
+ continue
420
+
421
+ if not func_stats.line_stats:
422
+ continue
423
+
424
+ # Use relative path if possible, otherwise basename
425
+ if self._project_folder is not None:
426
+ try:
427
+ display_path = Path(filename).resolve().relative_to(self._project_folder)
428
+ short_filename = str(display_path)
429
+ except (ValueError, OSError):
430
+ short_filename = Path(filename).name
431
+ else:
432
+ short_filename = Path(filename).name # type: ignore[unreachable]
433
+
434
+ for line_num, line_stats in func_stats.line_stats.items():
435
+ time_us = line_stats.total_time * 1e6
436
+
437
+ if time_us < min_time_us:
438
+ continue
439
+
440
+ all_lines.append({
441
+ "file": short_filename,
442
+ "function": function_name,
443
+ "line_num": line_num,
444
+ "hits": line_stats.hits,
445
+ "time_us": time_us,
446
+ "avg_time_us": line_stats.average_time * 1e6,
447
+ "percent": (line_stats.total_time / func_stats.total_time * 100
448
+ if func_stats.total_time > 0 else 0.0),
449
+ "source_line": func_stats.source_lines.get(line_num, ""),
450
+ })
451
+
452
+ if not all_lines:
453
+ print("No profiling data above the threshold.") # noqa: T201
454
+ return
455
+
456
+ # Sort by descending total time or hits
457
+ if sort_by == "hits":
458
+ all_lines.sort(key=lambda x: x["hits"], reverse=True)
459
+ else: # sort_by == "time"
460
+ all_lines.sort(key=lambda x: x["time_us"], reverse=True)
461
+
462
+ # Print header
463
+ print("=" * 130) # noqa: T201
464
+ print(f"Top {top_n} lines across all functions (sorted by {sort_by})") # noqa: T201
465
+ print("=" * 130) # noqa: T201
466
+ print(f"{'File::Function':<50} {'Line':<6} {'Hits':<10} {'Time (µs)':<13} " # noqa: T201
467
+ f"{'Per Hit (µs)':<14} {'% Time':<8} {'Line Content'}")
468
+ print("-" * 130) # noqa: T201
469
+
470
+ # Print top lines
471
+ for line in all_lines[:top_n]:
472
+ source_line = line["source_line"]
473
+ if len(source_line) > 40: # noqa: PLR2004
474
+ source_line = source_line[:37] + "..."
475
+
476
+ file_func = f"{line['file']}::{line['function']}"
477
+ if len(file_func) > 50: # noqa: PLR2004
478
+ file_func = file_func[:47] + "..."
479
+
480
+ print(f"{file_func:<50} {line['line_num']:<6} {line['hits']:<10} " # noqa: T201
481
+ f"{line['time_us']:<13.1f} {line['avg_time_us']:<14.1f} "
482
+ f"{line['percent']:<8.1f} {source_line}")
483
+
484
+ print("=" * 130) # noqa: T201
485
+ print() # noqa: T201
486
+
487
+ def get_stats(self) -> dict[tuple[str, str, int], FunctionStats]:
488
+ """Get raw profiling statistics.
489
+
490
+ Returns
491
+ -------
492
+ Dictionary mapping function keys to FunctionStats
493
+
494
+ Test components:
495
+ - Correct dictionary structure
496
+ - Immutability considerations (returns reference, not copy)
497
+ """
498
+ return self._function_stats
499
+
500
+ def clear(self) -> None:
501
+ """Clear all profiling data.
502
+
503
+ Test components:
504
+ - Complete state reset
505
+ - Proper dictionary clearing
506
+ """
507
+ self._function_stats.clear()
508
+ self._last_time = 0.0
509
+ self._last_line = None
510
+ self._current_function_key = None
511
+
512
+ def reset(self) -> None:
513
+ """Reset the profiler to initial state (alias for clear).
514
+
515
+ This method is an alias for clear() to provide a more intuitive
516
+ interface for users who think of "resetting" rather than "clearing".
517
+
518
+ Test components:
519
+ - Verify it calls clear() correctly
520
+ - Ensure all state is reset
521
+ - Check it's safe to call multiple times
522
+ """
523
+ self.clear()
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "with-line-profiler"
7
+ version = "0.1.0"
8
+ description = "Context-manager based line-by-line profiler for Python functions"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "mathematiger", email = "mcop.dkoehler@gmail.com" }
14
+ ]
15
+ keywords = ["profiler", "profiling", "performance", "timing", "debugging"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Software Development :: Debuggers",
26
+ "Topic :: Software Development :: Testing",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = [
30
+ "typing_extensions>=4.0.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/mathematiger/lineprofiler"
35
+ Repository = "https://github.com/mathematiger/lineprofiler"
36
+ Issues = "https://github.com/mathematiger/lineprofiler/issues"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["lineprofiler"]
40
+
41
+ [tool.ruff]
42
+ line-length = 100
43
+ target-version = "py39"
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "W", "I", "N", "UP", "ANN", "B", "C4", "SIM"]
47
+ ignore = ["ANN101", "ANN102"]
48
+
49
+ [tool.mypy]
50
+ python_version = "3.9"
51
+ strict = true
52
+ warn_return_any = true
53
+ warn_unused_ignores = true