Skip to content

Commit 64c0841

Browse files
ErotemicTest User
andauthored
Type fix base (#419)
* Remove stubs and add inline typing Fix ty type checking issues * Port latest util_static code * Remove line_profiler.pyi * Minor type fixes * Fix Cython test environment issues in dev container - Set PIP_NO_BINARY=Cython to build Cython from source, avoiding mmap issues - Fall back to non-editable install if editable install fails due to .so mapping errors - These changes fix compatibility with container environments that have memory mapping restrictions This allows the tests to run properly and fail on actual test logic rather than environment issues. * Fix cython regression * Fix type errors * Fix type annotations on 3.8 * Update reqs * Fix some types * Add future annots to tests * Fix type error in 38 * Fix another 38 type error * Revert test cython changes * Fix mypy errors * Fix mypy issues * Revert ipython types * Final fixes for mypy and ty checks * wip * Add typing ignore lines to coverage * fix * Fix --------- Co-authored-by: Test User <test@example.com>
1 parent 88590a0 commit 64c0841

55 files changed

Lines changed: 875 additions & 1300 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changes
22
=======
33

4+
5+
5.0.1
6+
~~~~~
7+
* ENH: improved type annotations and moved them inline
8+
9+
410
5.0.1
511
~~~~~
612
* FIX: Prevented duplicate or inconsistent profiler output under Python 3.14 when multiprocessing is used.

line_profiler/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from .line_profiler import main
24

35
if __name__ == '__main__':

line_profiler/__main__.pyi

Lines changed: 0 additions & 1 deletion
This file was deleted.

line_profiler/_diagnostics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ def _boolean_environ(
7171
"""
7272
# (TODO: migrate to `line_profiler.cli_utils.boolean()` after
7373
# merging #335)
74-
try:
75-
value = os.environ.get(envvar).casefold()
76-
except AttributeError: # None
74+
value = os.environ.get(envvar)
75+
if value is None:
7776
return default
77+
value = value.casefold()
7878
non_default_values = falsy if default else truey
7979
if value in {v.casefold() for v in non_default_values}:
8080
return not default

line_profiler/_line_profiler.pyi

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Mapping
4+
5+
6+
class LineStats:
7+
timings: Mapping[tuple[str, int, str], list[tuple[int, int, int]]]
8+
unit: float
9+
10+
def __init__(
11+
self,
12+
timings: Mapping[tuple[str, int, str], list[tuple[int, int, int]]],
13+
unit: float,
14+
) -> None: ...
15+
16+
17+
class LineProfiler:
18+
def enable_by_count(self) -> None: ...
19+
def disable_by_count(self) -> None: ...
20+
def add_function(self, func: Any) -> None: ...
21+
def get_stats(self) -> LineStats: ...
22+
def dump_stats(self, filename: str) -> None: ...
23+
24+
25+
def label(code: Any) -> Any: ...

line_profiler/autoprofile/ast_profile_transformer.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
from __future__ import annotations
2+
13
import ast
4+
from typing import cast, Union, List
25

36

4-
def ast_create_profile_node(modname, profiler_name='profile', attr='add_imported_function_or_module'):
7+
def ast_create_profile_node(
8+
modname: str, profiler_name: str = 'profile',
9+
attr: str = 'add_imported_function_or_module') -> ast.Expr:
510
"""Create an abstract syntax tree node that adds an object to the profiler to be profiled.
611
712
An abstract syntax tree node is created which calls the attr method from profile and
@@ -29,7 +34,7 @@ def ast_create_profile_node(modname, profiler_name='profile', attr='add_imported
2934
"""
3035
func = ast.Attribute(value=ast.Name(id=profiler_name, ctx=ast.Load()), attr=attr, ctx=ast.Load())
3136
names = modname.split('.')
32-
value = ast.Name(id=names[0], ctx=ast.Load())
37+
value: ast.expr = ast.Name(id=names[0], ctx=ast.Load())
3338
for name in names[1:]:
3439
value = ast.Attribute(attr=name, ctx=ast.Load(), value=value)
3540
expr = ast.Expr(value=ast.Call(func=func, args=[value], keywords=[]))
@@ -45,7 +50,9 @@ class AstProfileTransformer(ast.NodeTransformer):
4550
immediately after the import.
4651
"""
4752

48-
def __init__(self, profile_imports=False, profiled_imports=None, profiler_name='profile'):
53+
def __init__(self, profile_imports: bool = False,
54+
profiled_imports: list[str] | None = None,
55+
profiler_name: str = 'profile') -> None:
4956
"""Initializes the AST transformer with the profiler name.
5057
5158
Args:
@@ -63,7 +70,9 @@ def __init__(self, profile_imports=False, profiled_imports=None, profiler_name='
6370
self._profiled_imports = profiled_imports if profiled_imports is not None else []
6471
self._profiler_name = profiler_name
6572

66-
def _visit_func_def(self, node):
73+
def _visit_func_def(
74+
self, node: ast.FunctionDef | ast.AsyncFunctionDef
75+
) -> ast.FunctionDef | ast.AsyncFunctionDef:
6776
"""Decorate functions/methods with profiler.
6877
6978
Checks if the function/method already has a profile_name decorator, if not, it will append
@@ -81,17 +90,19 @@ def _visit_func_def(self, node):
8190
"""
8291
decor_ids = set()
8392
for decor in node.decorator_list:
84-
try:
93+
if isinstance(decor, ast.Name):
8594
decor_ids.add(decor.id)
86-
except AttributeError:
87-
...
8895
if self._profiler_name not in decor_ids:
8996
node.decorator_list.append(ast.Name(id=self._profiler_name, ctx=ast.Load()))
90-
return self.generic_visit(node)
97+
self.generic_visit(node)
98+
return node
9199

92100
visit_FunctionDef = visit_AsyncFunctionDef = _visit_func_def
93101

94-
def _visit_import(self, node):
102+
def _visit_import(
103+
self, node: ast.Import | ast.ImportFrom
104+
) -> (ast.Import | ast.ImportFrom
105+
| list[ast.Import | ast.ImportFrom | ast.Expr]):
95106
"""Add a node that profiles an import
96107
97108
If profile_imports is True and the import is not in profiled_imports,
@@ -110,8 +121,10 @@ def _visit_import(self, node):
110121
returns list containing the import node and the profiling node
111122
"""
112123
if not self._profile_imports:
113-
return self.generic_visit(node)
114-
visited = [self.generic_visit(node)]
124+
self.generic_visit(node)
125+
return node
126+
this_visit = cast(Union[ast.Import, ast.ImportFrom], self.generic_visit(node))
127+
visited: list[ast.Import | ast.ImportFrom | ast.Expr] = [this_visit]
115128
for names in node.names:
116129
node_name = names.name if names.asname is None else names.asname
117130
if node_name in self._profiled_imports:
@@ -121,7 +134,9 @@ def _visit_import(self, node):
121134
visited.append(expr)
122135
return visited
123136

124-
def visit_Import(self, node):
137+
def visit_Import(
138+
self, node: ast.Import
139+
) -> ast.Import | list[ast.Import | ast.Expr]:
125140
"""Add a node that profiles an object imported using the "import foo" sytanx
126141
127142
Args:
@@ -135,9 +150,12 @@ def visit_Import(self, node):
135150
if profile_imports is True:
136151
returns list containing the import node and the profiling node
137152
"""
138-
return self._visit_import(node)
153+
return cast(Union[ast.Import, List[Union[ast.Import, ast.Expr]]],
154+
self._visit_import(node))
139155

140-
def visit_ImportFrom(self, node):
156+
def visit_ImportFrom(
157+
self, node: ast.ImportFrom
158+
) -> ast.ImportFrom | list[ast.ImportFrom | ast.Expr]:
141159
"""Add a node that profiles an object imported using the "from foo import bar" syntax
142160
143161
Args:
@@ -151,4 +169,5 @@ def visit_ImportFrom(self, node):
151169
if profile_imports is True:
152170
returns list containing the import node and the profiling node
153171
"""
154-
return self._visit_import(node)
172+
return cast(Union[ast.ImportFrom, List[Union[ast.ImportFrom, ast.Expr]]],
173+
self._visit_import(node))

line_profiler/autoprofile/ast_profile_transformer.pyi

Lines changed: 0 additions & 36 deletions
This file was deleted.

line_profiler/autoprofile/ast_tree_profiler.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from __future__ import annotations
2+
13
import ast
24
import os
5+
from typing import Type
36

47
from .ast_profile_transformer import (AstProfileTransformer,
58
ast_create_profile_node)
@@ -20,11 +23,11 @@ class AstTreeProfiler:
2023
"""
2124

2225
def __init__(self,
23-
script_file,
24-
prof_mod,
25-
profile_imports,
26-
ast_transformer_class_handler=AstProfileTransformer,
27-
profmod_extractor_class_handler=ProfmodExtractor):
26+
script_file: str,
27+
prof_mod: list[str],
28+
profile_imports: bool,
29+
ast_transformer_class_handler: Type = AstProfileTransformer,
30+
profmod_extractor_class_handler: Type = ProfmodExtractor) -> None:
2831
"""Initializes the AST tree profiler instance with the script file path
2932
3033
Args:
@@ -52,7 +55,8 @@ def __init__(self,
5255
self._profmod_extractor_class_handler = profmod_extractor_class_handler
5356

5457
@staticmethod
55-
def _check_profile_full_script(script_file, prof_mod):
58+
def _check_profile_full_script(
59+
script_file: str, prof_mod: list[str]) -> bool:
5660
"""Check whether whole script should be profiled.
5761
5862
Checks whether path to script has been passed to prof_mod indicating that
@@ -76,7 +80,7 @@ def _check_profile_full_script(script_file, prof_mod):
7680
return profile_full_script
7781

7882
@staticmethod
79-
def _get_script_ast_tree(script_file):
83+
def _get_script_ast_tree(script_file: str) -> ast.Module:
8084
"""Generate an abstract syntax from a script file.
8185
8286
Args:
@@ -93,10 +97,10 @@ def _get_script_ast_tree(script_file):
9397
return tree
9498

9599
def _profile_ast_tree(self,
96-
tree,
97-
tree_imports_to_profile_dict,
98-
profile_full_script=False,
99-
profile_imports=False):
100+
tree: ast.Module,
101+
tree_imports_to_profile_dict: dict[int, str],
102+
profile_full_script: bool = False,
103+
profile_imports: bool = False) -> ast.Module:
100104
"""Add profiling to an abstract syntax tree.
101105
102106
Adds nodes to the AST that adds the specified objects to the profiler.
@@ -139,7 +143,7 @@ def _profile_ast_tree(self,
139143
ast.fix_missing_locations(tree)
140144
return tree
141145

142-
def profile(self):
146+
def profile(self) -> ast.Module:
143147
"""Create an abstract syntax tree of a script and add profiling to it.
144148
145149
Reads a script file and generates an abstract syntax tree.

line_profiler/autoprofile/ast_tree_profiler.pyi

Lines changed: 0 additions & 23 deletions
This file was deleted.

line_profiler/autoprofile/autoprofile.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,16 @@ def main():
4444
python -m kernprof -p demo.py -l demo.py
4545
python -m line_profiler -rmt demo.py.lprof
4646
"""
47+
from __future__ import annotations
4748
import contextlib
4849
import functools
4950
import importlib.util
5051
import operator
5152
import sys
5253
import types
54+
from collections.abc import MutableMapping
55+
from typing import Any, cast, Dict, Mapping
56+
from typing import ContextManager
5357
from .ast_tree_profiler import AstTreeProfiler
5458
from .run_module import AstTreeModuleProfiler
5559
from .line_profiler_utils import add_imported_function_or_module
@@ -58,7 +62,7 @@ def main():
5862
PROFILER_LOCALS_NAME = 'prof'
5963

6064

61-
def _extend_line_profiler_for_profiling_imports(prof):
65+
def _extend_line_profiler_for_profiling_imports(prof: Any) -> None:
6266
"""Allow profiler to handle functions/methods, classes & modules with a single call.
6367
6468
Add a method to LineProfiler that can identify whether the object is a
@@ -73,7 +77,9 @@ def _extend_line_profiler_for_profiling_imports(prof):
7377
prof.add_imported_function_or_module = types.MethodType(add_imported_function_or_module, prof)
7478

7579

76-
def run(script_file, ns, prof_mod, profile_imports=False, as_module=False):
80+
def run(script_file: str, ns: MutableMapping[str, Any],
81+
prof_mod: list[str], profile_imports: bool = False,
82+
as_module: bool = False) -> None:
7783
"""Automatically profile a script and run it.
7884
7985
Profile functions, classes & modules specified in prof_mod without needing to add
@@ -98,21 +104,26 @@ def run(script_file, ns, prof_mod, profile_imports=False, as_module=False):
98104
Whether we're running script_file as a module
99105
"""
100106
class restore_dict:
101-
def __init__(self, d, target=None):
107+
def __init__(self, d: MutableMapping[str, Any], target=None):
102108
self.d = d
103109
self.target = target
104-
self.copy = None
110+
self.copy: Mapping[str, Any] | None = None
105111

106112
def __enter__(self):
107113
assert self.copy is None
108-
self.copy = self.d.copy()
114+
self.copy = dict(self.d)
109115
return self.target
110116

111117
def __exit__(self, *_, **__):
112118
self.d.clear()
113-
self.d.update(self.copy)
119+
if self.copy is not None:
120+
self.d.update(self.copy)
114121
self.copy = None
115122

123+
Profiler: type[AstTreeModuleProfiler] | type[AstTreeProfiler]
124+
namespace: MutableMapping[str, Any]
125+
ctx: ContextManager
126+
116127
if as_module:
117128
Profiler = AstTreeModuleProfiler
118129
module_name = modpath_to_modname(script_file)
@@ -144,4 +155,4 @@ def __exit__(self, *_, **__):
144155
code_obj = compile(tree_profiled, script_file, 'exec')
145156
with ctx as callback:
146157
callback()
147-
exec(code_obj, namespace, namespace)
158+
exec(code_obj, cast(Dict[str, Any], namespace), namespace)

0 commit comments

Comments
 (0)