Skip to content

Commit

Permalink
Rework linecache handling (#828)
Browse files Browse the repository at this point in the history
* Rework linecache handling

* lint

* Add changelog
  • Loading branch information
Tinche authored Jun 30, 2021
1 parent fb15487 commit 3858063
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 36 deletions.
1 change: 1 addition & 0 deletions changelog.d/828.changes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generated source code is now cached more efficiently for identical classes.
56 changes: 23 additions & 33 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import linecache
import sys
import threading
import uuid
import warnings

from operator import itemgetter
Expand Down Expand Up @@ -329,16 +328,25 @@ def _make_method(name, script, filename, globs=None):
if globs is None:
globs = {}

_compile_and_eval(script, globs, locs, filename)

# In order of debuggers like PDB being able to step through the code,
# we add a fake linecache entry.
linecache.cache[filename] = (
len(script),
None,
script.splitlines(True),
filename,
)
count = 1
base_filename = filename
while True:
linecache_tuple = (
len(script),
None,
script.splitlines(True),
filename,
)
old_val = linecache.cache.setdefault(filename, linecache_tuple)
if old_val == linecache_tuple:
break
else:
filename = "{}-{}>".format(base_filename[:-1], count)
count += 1

_compile_and_eval(script, globs, locs, filename)

return locs[name]

Expand Down Expand Up @@ -1632,30 +1640,12 @@ def _generate_unique_filename(cls, func_name):
"""
Create a "filename" suitable for a function being generated.
"""
unique_id = uuid.uuid4()
extra = ""
count = 1

while True:
unique_filename = "<attrs generated {0} {1}.{2}{3}>".format(
func_name,
cls.__module__,
getattr(cls, "__qualname__", cls.__name__),
extra,
)
# To handle concurrency we essentially "reserve" our spot in
# the linecache with a dummy line. The caller can then
# set this value correctly.
cache_line = (1, None, (str(unique_id),), unique_filename)
if (
linecache.cache.setdefault(unique_filename, cache_line)
== cache_line
):
return unique_filename

# Looks like this spot is taken. Try again.
count += 1
extra = "-{0}".format(count)
unique_filename = "<attrs generated {0} {1}.{2}>".format(
func_name,
cls.__module__,
getattr(cls, "__qualname__", cls.__name__),
)
return unique_filename


def _make_hash(cls, attrs, frozen, cache_hash):
Expand Down
28 changes: 25 additions & 3 deletions tests/test_dunders.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,16 @@ class C(object):
pass


CopyC = C


@attr.s(hash=True, order=True)
class C(object):
"""A different class, to generate different methods."""

a = attr.ib()


class TestFilenames(object):
def test_filenames(self):
"""
Expand All @@ -953,15 +963,27 @@ def test_filenames(self):
OriginalC.__hash__.__code__.co_filename
== "<attrs generated hash tests.test_dunders.C>"
)
assert (
CopyC.__init__.__code__.co_filename
== "<attrs generated init tests.test_dunders.C>"
)
assert (
CopyC.__eq__.__code__.co_filename
== "<attrs generated eq tests.test_dunders.C>"
)
assert (
CopyC.__hash__.__code__.co_filename
== "<attrs generated hash tests.test_dunders.C>"
)
assert (
C.__init__.__code__.co_filename
== "<attrs generated init tests.test_dunders.C-2>"
== "<attrs generated init tests.test_dunders.C-1>"
)
assert (
C.__eq__.__code__.co_filename
== "<attrs generated eq tests.test_dunders.C-2>"
== "<attrs generated eq tests.test_dunders.C-1>"
)
assert (
C.__hash__.__code__.co_filename
== "<attrs generated hash tests.test_dunders.C-2>"
== "<attrs generated hash tests.test_dunders.C-1>"
)

0 comments on commit 3858063

Please sign in to comment.