Skip to content

Commit 88d4758

Browse files
authored
Optimize linecaching (#464)
* Optimize linecaching * Fix annotation
1 parent 5b1fa6a commit 88d4758

File tree

5 files changed

+42
-31
lines changed

5 files changed

+42
-31
lines changed

Diff for: HISTORY.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Fix a regression when unstructuring dictionary values typed as `Any`.
66
([#453](https://github.com/python-attrs/cattrs/issues/453) [#462](https://github.com/python-attrs/cattrs/pull/462))
7+
- Optimize function source code caching.
8+
([#445](https://github.com/python-attrs/cattrs/issues/445))
79
- Generate unique files only in case of linecache enabled.
810
([#445](https://github.com/python-attrs/cattrs/issues/445) [#441](https://github.com/python-attrs/cattrs/pull/461))
911

Diff for: src/cattrs/gen/__init__.py

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import linecache
43
import re
54
from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, Tuple, TypeVar
65

@@ -212,12 +211,9 @@ def make_dict_unstructure_fn(
212211
+ [" return res"]
213212
)
214213
script = "\n".join(total_lines)
215-
fname = ""
216-
if _cattrs_use_linecache:
217-
fname = generate_unique_filename(
218-
cl, "unstructure", reserve=_cattrs_use_linecache
219-
)
220-
linecache.cache[fname] = len(script), None, total_lines, fname
214+
fname = generate_unique_filename(
215+
cl, "unstructure", lines=total_lines if _cattrs_use_linecache else []
216+
)
221217

222218
eval(compile(script, fname, "exec"), globs)
223219
finally:
@@ -627,10 +623,9 @@ def make_dict_structure_fn(
627623
]
628624

629625
script = "\n".join(total_lines)
630-
fname = ""
631-
if _cattrs_use_linecache:
632-
fname = generate_unique_filename(cl, "structure", reserve=_cattrs_use_linecache)
633-
linecache.cache[fname] = len(script), None, total_lines, fname
626+
fname = generate_unique_filename(
627+
cl, "structure", lines=total_lines if _cattrs_use_linecache else []
628+
)
634629

635630
eval(compile(script, fname, "exec"), globs)
636631

Diff for: src/cattrs/gen/_lc.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
"""Line-cache functionality."""
22
import linecache
3-
import uuid
4-
from typing import Any
3+
from typing import Any, List
54

65

7-
def generate_unique_filename(cls: Any, func_name: str, reserve: bool = True) -> str:
6+
def generate_unique_filename(cls: Any, func_name: str, lines: List[str] = []) -> str:
87
"""
98
Create a "filename" suitable for a function being generated.
9+
10+
If *lines* are provided, insert them in the first free spot or stop
11+
if a duplicate is found.
1012
"""
11-
unique_id = uuid.uuid4()
1213
extra = ""
1314
count = 1
1415

1516
while True:
1617
unique_filename = "<cattrs generated {} {}.{}{}>".format(
1718
func_name, cls.__module__, getattr(cls, "__qualname__", cls.__name__), extra
1819
)
19-
if not reserve:
20+
if not lines:
2021
return unique_filename
21-
# To handle concurrency we essentially "reserve" our spot in
22-
# the linecache with a dummy line. The caller can then
23-
# set this value correctly.
24-
cache_line = (1, None, (str(unique_id),), unique_filename)
22+
cache_line = (len("\n".join(lines)), None, lines, unique_filename)
2523
if linecache.cache.setdefault(unique_filename, cache_line) == cache_line:
2624
return unique_filename
2725

Diff for: src/cattrs/gen/typeddicts.py

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import linecache
43
import re
54
import sys
65
from typing import TYPE_CHECKING, Any, Callable, TypeVar
@@ -225,12 +224,9 @@ def make_dict_unstructure_fn(
225224
]
226225
script = "\n".join(total_lines)
227226

228-
fname = ""
229-
if _cattrs_use_linecache:
230-
fname = generate_unique_filename(
231-
cl, "unstructure", reserve=_cattrs_use_linecache
232-
)
233-
linecache.cache[fname] = len(script), None, total_lines, fname
227+
fname = generate_unique_filename(
228+
cl, "unstructure", lines=total_lines if _cattrs_use_linecache else []
229+
)
234230

235231
eval(compile(script, fname, "exec"), globs)
236232
finally:
@@ -523,10 +519,9 @@ def make_dict_structure_fn(
523519
]
524520

525521
script = "\n".join(total_lines)
526-
fname = ""
527-
if _cattrs_use_linecache:
528-
fname = generate_unique_filename(cl, "structure", reserve=_cattrs_use_linecache)
529-
linecache.cache[fname] = len(script), None, total_lines, fname
522+
fname = generate_unique_filename(
523+
cl, "structure", lines=total_lines if _cattrs_use_linecache else []
524+
)
530525

531526
eval(compile(script, fname, "exec"), globs)
532527
return globs[fn_name]

Diff for: tests/test_gen.py

+21
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,24 @@ class B:
7070
c.structure(c.unstructure(B(1)), B)
7171

7272
assert len(linecache.cache) == before
73+
74+
75+
def test_linecache_dedup():
76+
"""Linecaching avoids duplicates."""
77+
78+
@define
79+
class LinecacheA:
80+
a: int
81+
82+
c = Converter()
83+
before = len(linecache.cache)
84+
c.structure(c.unstructure(LinecacheA(1)), LinecacheA)
85+
after = len(linecache.cache)
86+
87+
assert after == before + 2
88+
89+
c = Converter()
90+
91+
c.structure(c.unstructure(LinecacheA(1)), LinecacheA)
92+
93+
assert len(linecache.cache) == after

0 commit comments

Comments
 (0)