Skip to content

Fixed #29478 -- Added support for mangled names to cached_property. - #10033

Merged
timgraham merged 1 commit into
django:masterfrom
graingert:mangled-cached-property
Nov 19, 2018
Merged

timgraham merged 1 commit into
django:masterfrom
graingert:mangled-cached-property

Conversation

@graingert

@graingert graingert commented Jun 7, 2018 •

Copy link
Copy Markdown
Contributor

@graingert
graingert force-pushed the mangled-cached-property branch 2 times, most recently from 4b6aca6 to bf0148d Compare June 7, 2018 13:12
@graingert graingert changed the title support mangled chached properties support mangled cached properties Jun 7, 2018
@graingert
graingert force-pushed the mangled-cached-property branch 8 times, most recently from 2141388 to e7b457b Compare June 7, 2018 19:10
Comment thread django/utils/functional.py Outdated

@graingert graingert Jun 7, 2018 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should throw a warning on < PY36 where re.startswith('__') and not re.endswith('__')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the best way to do that though

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a plain warning would be fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

People like to copy this file into their non Django related files. I'd like to avoid importing Django here for that reason

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a plain warnings.warn

Also not sure that the 'people like to copy this file' argument holds for Django's codebase. There's pip install cached-property for an implementation of this outside of Django.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually this is redundant because PY35 is no longer supported:

eg I can't do: if django.utils.version.PY35 and name is None and func.__name__.startwith('__') and not func.__name__.startwith('__') anyway

@adamchainz adamchainz changed the title support mangled cached properties Fixed #29478 -- Added support for mangled names to @cached_property Jun 7, 2018

@adamchainz adamchainz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also need a release note in the 2.2 release note docs, and maybe we need to mark name for deprecation starting with the future version of Django that supports only 3.6+ (which might be 3.0?)

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap this into previous paragraph so we only have one explanation of name?

On Python <3.6, the ``name`` argument should be passed to make a cached property from another method without a name collision, e.g. ``url = cached_property(get_absolute_url, name='url')``.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a plain warning would be fine.

Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'd normally call this check_cached_property or similar

Also I'd prefer a longer class name than A whilst we're refactoring these tests

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically follow the camel casing of unittest assertions, e.g. assertCachedPropertyWorks. Try to split out test refactoring in a separate PR to ease review.

@graingert graingert Jun 8, 2018 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently the refactorings are in separate commits, here's the copy/paste version: 0870388

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timgraham I've also included a call to with self.subTest

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just do name.startswith('__') and not name.endswith('__')? Simpler is better

Comment thread django/utils/version.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PY35

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooops

@graingert
graingert force-pushed the mangled-cached-property branch 2 times, most recently from c02fa92 to 96ac758 Compare June 27, 2018 13:41
@graingert

Copy link
Copy Markdown
Contributor Author

release note added

@graingert
graingert force-pushed the mangled-cached-property branch from f4c6661 to 7c03011 Compare June 27, 2018 17:16

@timgraham timgraham left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be a warning or an error? In either case, please test the warning/error case.

Comment thread docs/releases/2.2.txt Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what Adam was thinking of with regards to the release note, but we don't usually document changes in django.utils to be "minor features".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know where it should go?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely it doesn't need a release unless there's some backwards incompatible change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen a lot of use of @cached_property so I thought it would count, but yeah let's not worry about it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be documented when 3.5 is dropped and name is deprecated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@graingert

Copy link
Copy Markdown
Contributor Author

Should it be a warning or an error? In either case, please test the warning/error case.

Probably should not be an Error, because just upgrading to Python 3.6 will fix it

@graingert
graingert force-pushed the mangled-cached-property branch from 7c03011 to c22b6f7 Compare June 28, 2018 08:33
@sir-sigurd

Copy link
Copy Markdown
Member

Can't we simply construct mangled name in cached_propery.__get__() to make it work on Python 3.5 as well?

@graingert

Copy link
Copy Markdown
Contributor Author

@sir-sigurd nope, because you don't know which class to mangle the name with

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graingert cls is passed here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah but it's the wrong cls

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's the right class:

In [38]: class desc:
    ...:     def __get__(self, instance, cls):
    ...:         if instance is None:
    ...:             return self
    ...:         return instance.__dict__['_%s__mangled' % cls.__name__]

In [39]: class A:
    ...:     d = desc()
    ...: 
    ...:     def __init__(self):
    ...:         self.__mangled = 42
    ...: 

In [40]: A().d
Out[40]: 42

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try with a subclass of A

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to add tests for the subclass case @graingert to make it clear.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graingert Probably it doesn't worth it, but still doable :-)

In [85]: class desc:
    ...:     def __init__(self, f):
    ...:         self.name = f.__name__
    ...: 
    ...:     def __get__(self, instance, cls):
    ...:         if instance is None:
    ...:             return self
    ...: 
    ...:         for cls in cls.__mro__:
    ...:             name = '_%s%s' % (cls.__name__, self.name)
    ...:             if cls.__dict__.get(name) is self:
    ...:                 return name
    ...: 
    ...: class A:
    ...:     @desc
    ...:     def __x(self):
    ...:         pass
    ...: 
    ...: 
    ...: class B(A):
    ...:     pass
    ...: 

In [86]: a = A()
    ...: a._A__x
    ...: 
Out[86]: '_A__x'

In [87]: b = B()
    ...: b._A__x
    ...: 
Out[87]: '_A__x'

@graingert
graingert force-pushed the mangled-cached-property branch from 1bb4639 to 59f1483 Compare June 28, 2018 12:25
@graingert
graingert force-pushed the mangled-cached-property branch 4 times, most recently from 78dd2a9 to d0e1111 Compare June 29, 2018 08:54
Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to branch off greater or equal to Python 3.6 here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's how this works already

Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to branch of lesser than Python 3.6 here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's how this works already

Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python < 3.6.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to add tests for the subclass case @graingert to make it clear.

Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this helper is making the assertion harder to reason about. Inlining getattr(source, attr) calls would be easier to read IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it gets very long winded when you add getattr(source, attr) eveywhere

Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the rationale for changing this test class name? It looks like unnecessary noise to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamchainz said to do it: #10033 (comment)

Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto about the class __name__ change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__name__?

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say on Python < 3.6

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to branch off < 3.6.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's how this works

@adamchainz

Copy link
Copy Markdown
Member

@timgraham

Copy link
Copy Markdown
Member

If I understand correctly, the documentation for cached_property, including the warning added in 0919973 should be updated to say that name is only required for Python < 3.6.

@graingert

Copy link
Copy Markdown
Contributor Author

Can you suggest a wording?

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to check explicitly set name too, because '__name' obviously will not work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class A:
    __print = cached_property(print, '__print')

This will not work and we can easily detect it too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should the condition be?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sir-sigurd can you advise about the changes to address your comments?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll push my edits tomorrow.

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the warning, but not an exception?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semver

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also still don't understand why it's useful to allow writing code that doesn't work.

@graingert graingert Jul 13, 2018 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well it does work, it just behaves as though @cached_property were not there. And it writes to some self.__ attr

@charettes charettes Jul 13, 2018 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think silently failing to cache the property should be considered not working.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider that as not working

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should the exception be?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValueError maybe since passing along name can fix it?

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to raise exception in this case:

class Test:
    @cached_property
    def a(self):
        pass
    b = a

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

@graingert
graingert force-pushed the mangled-cached-property branch from 6e5402a to 89e7707 Compare July 13, 2018 16:02
@sir-sigurd

Copy link
Copy Markdown
Member

I added more checks, name kwarg is now ignored on Python >= 3.6. Some new tests are blindly copied from python/cpython#6982.

@timgraham
timgraham force-pushed the mangled-cached-property branch from 384a904 to 91b16f7 Compare September 18, 2018 14:37
@timgraham

Copy link
Copy Markdown
Member

Looks like Jenkins didn't run correctly on the latest commits. After I squashed and pushed some cosmetic changes, there are errors like "TypeError: Cannot assign the same cached_property to two different names ('is_postgresql_9_5' and 'has_select_for_update_skip_locked')" (which were there before my latest changes).

@sir-sigurd

Copy link
Copy Markdown
Member

We need to fix it because some attributes are not cached.

For example here:

@cached_property
def is_postgresql_9_5(self):
return self.connection.pg_version >= 90500
@cached_property
def is_postgresql_9_6(self):
return self.connection.pg_version >= 90600
@cached_property
def is_postgresql_10(self):
return self.connection.pg_version >= 100000
has_select_for_update_skip_locked = is_postgresql_9_5
has_brin_index_support = is_postgresql_9_5
has_jsonb_agg = is_postgresql_9_5
has_brin_autosummarize = is_postgresql_10
has_gin_pending_list_limit = is_postgresql_9_5
supports_ignore_conflicts = is_postgresql_9_5
has_phraseto_tsquery = is_postgresql_9_6

only is_postgresql_x are cached, is_postgresql_9_5() is called every time you access has_select_for_update_skip_locked. Should I fix it in this PR?

Probably we need to add it to release notes as breaking change and give example when it doesn't work as expected for Python 3.5 users.

@timgraham

Copy link
Copy Markdown
Member

Either in this PR or in another one seems fine. Thanks!

Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW the TypeError raised here would be backward incompatible. Such a pattern is even used in Django core.

@cached_property
def is_postgresql_9_5(self):
return self.connection.pg_version >= 90500
@cached_property
def is_postgresql_9_6(self):
return self.connection.pg_version >= 90600
@cached_property
def is_postgresql_10(self):
return self.connection.pg_version >= 100000
has_select_for_update_skip_locked = is_postgresql_9_5
has_brin_index_support = is_postgresql_9_5
has_jsonb_agg = is_postgresql_9_5
has_brin_autosummarize = is_postgresql_10
has_gin_pending_list_limit = is_postgresql_9_5
supports_ignore_conflicts = is_postgresql_9_5
has_phraseto_tsquery = is_postgresql_9_6

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, as Sergey said above, there's a bug where such attributes aren't cached.

@charettes charettes Nov 7, 2018 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh right just read through the whole discussion.

I wonder if this could be an opportunity to solve the issue.

e.g.

def __set_name__(self, owner, name):
    if self.name is None:
        self.name = name
        self.func = self.real_func
    elif name != self.name:
        # Re-assignment of a cached property to a class
        # creates a new instance.
        instance = type(self)(self.real_func, name)
        setattr(owner, name, instance)

That would make

@cached_property
def is_postgresql_9_5(self): 
    return self.connection.pg_version >= 90500

has_select_for_update_skip_locked = is_postgresql_9_5

The equivalent of

@cached_property
def is_postgresql_9_5(self): 
    return self.connection.pg_version >= 90500

has_select_for_update_skip_locked = cached_property(
    is_postgresql_9_5.real_func, 'has_select_for_update_skip_locked'
)

And address the bug preventing the __dict__ level caching bug discovered by @sir-sigurd.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this could be an opportunity to solve the issue.

I considered it, but:

  1. it doesn't fix caching on Python 3.5, raising TypeError on Python 3.6 could useful when code is tested against both versions (for example we wouldn't know that we use such pattern that works incorrectly on Python 3.5)
  2. I'm not sure if we want to make it work this way, we might want this instead:
@cached_property
def is_postgresql_9_5(self): 
    return self.connection.pg_version >= 90500

has_select_for_update_skip_locked = property(
    attrgetter('is_postgresql_9_5')
)

This way touching both is_postgresql_9_5 and has_select_for_update_skip_locked will cause is_postgresql_9_5.real_func be called just once.
We might add cached_property.alias() method for functionality.

@timgraham

Copy link
Copy Markdown
Member

I'll add a release note to the first commit to describe the change of not allowing cached property aliases (which weren't cached anyway) and recommended the property(operator.attrgetter()) approach?

@sir-sigurd

Copy link
Copy Markdown
Member

recommended the property(operator.attrgetter()) approach?

I'm going to implement alias() method I described above which could become recommended approach, but since it's not ready, let's do it.

@charettes

charettes commented Nov 16, 2018 •

Copy link
Copy Markdown
Member

@sir-sigurd one thing to consider with alias() is that cached_property happens to be settable (and deletable when cached as well while the current suggested implementation isn't.

Not a blocker here as it will be implemented in another PR but definitely something to consider documenting or adapting to support both. Something also worth nothing is that the suggested implementation will probably be a bit slower to retrieve a cached value from an alias because property is a data descriptor and thus attribute accesses won't perform a quick __dict__ lookup like direct cached_property accesses. In other words, the attrgetter call will always be performed.

@timgraham
timgraham force-pushed the mangled-cached-property branch from cb931ab to 8810cfa Compare November 17, 2018 23:31
Comment thread django/utils/functional.py Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this will raise even if name kwarg is set

Comment thread tests/utils_tests/test_functional.py Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to put the name kwarg on the decorator?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's intended. Based on the documentation, a valid name has to be in the form _Class__attribute. That said, unless I've made a mistake it doesn't seem to work (Python 3.5):

from django.utils.functional import cached_property

class Obj:
    @cached_property
    def __value1(self, name='_Obj__value1'):
        pass

(gives ValueError: cached_property does not work with mangled methods on Python < 3.6 without the appropriate name argument.)
Is it a bug or did I misunderstand something, Sergey?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're putting it on the decorated method, rather than as a decorator kwarg

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

args on property methods are redundant

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. It seems there's still a bug since the test isn't correct. For example, the documented example doesn't require the name argument despite the fact that it says it does:

class Person:
    def get_friends(self):
        pass
    __friends = cached_property(get_friends)

No exception for Python 3.5 but docs say name='_Person__friends' is required.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No exception for Python 3.5 but docs say name='_Person__friends' is required.

It's required because it won't be cached and no exception is raised because there is no way to detect it.

@timgraham
timgraham dismissed adamchainz’s stale review November 19, 2018 15:48

comments addressed

@timgraham
timgraham force-pushed the mangled-cached-property branch from 1400048 to 89c9aff Compare November 19, 2018 15:57
@timgraham timgraham changed the title Fixed #29478 -- Added support for mangled names to @cached_property Fixed #29478 -- Added support for mangled names to cached_property. Nov 19, 2018
Co-Authored-By: Sergey Fedoseev <fedoseev.sergey@gmail.com>
@timgraham
timgraham force-pushed the mangled-cached-property branch from 89c9aff to 0607699 Compare November 19, 2018 18:41
@timgraham
timgraham merged commit 0607699 into django:master Nov 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants