Skip to content

Instantly share code, notes, and snippets.

@Suor
Created March 22, 2023 11:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Suor/af424c3501792dba6fcf907506987571 to your computer and use it in GitHub Desktop.
Save Suor/af424c3501792dba6fcf907506987571 to your computer and use it in GitHub Desktop.
Trying to make ChatGPT to write a blog post

> Write a blog post about this code trick:

import logging
import os

from funcy import monkey

from django.db.models.query_utils import DeferredAttribute


logger = logging.getLogger(__name__)


@monkey(DeferredAttribute, "__get__")
def _DeferredAttribute_get(self, instance, cls=None):
    if instance is None:
        return self
    data = instance.__dict__
    field_name = self.field.attname

    # Normally this accessor won't be called if field_name is in __dict__,
    # we need this part so that DeferredAttribute descendants with __set__ play nice.
    if field_name in data:
        return data[field_name]

    # If it's not there already then prevent an SQL query or at least notify we are doing smth bad
    attr = f"{instance.__class__.__name__}.{field_name}"
    # Only trigger this check once per attr to not flood Sentry with identical messages
    if not _seen(attr):
        message = f"Lazy fetching of {attr} may cause 1+N issue"
        # We stop in DEBUG mode and if inside tests but let production to proceed.
        # Using LookupError instead of AttributeError here to prevent higher level "handling" this.
        if os.environ["DEBUG"] == "True" or "PYTEST_CURRENT_TEST" in os.environ:
            raise LookupError(message)
        else:
            logger.exception(message)

    # Proceed normally
    return _DeferredAttribute_get.original(self, instance, cls)


def _seen(key, _memory=set()):
    seen = key in _memory
    _memory.add(key)
    return seen

The code trick you've presented is a monkey patching technique used to modify the behavior of the DeferredAttribute class in Django. Monkey patching is a technique in which the behavior of an existing module or class is modified at runtime by injecting new code into it.

In Django, a DeferredAttribute is a class that is used to represent a field that has not yet been loaded from the database. When an attribute is accessed on an instance of a model that contains a DeferredAttribute, Django will automatically fetch the value of that attribute from the database. This behavior is convenient, but it can also lead to performance problems if it is not used carefully.

The monkey patch in this code snippet modifies the behavior of the DeferredAttribute class so that it logs a warning message whenever an attribute is accessed that has not yet been loaded from the database. This warning message is intended to alert developers to the potential performance problems associated with lazy loading.

Here's how the code works:

  1. The code imports the logging and os modules, as well as the DeferredAttribute class from Django and the monkey function from the funcy module.

  2. It sets up a logger object to log warning messages. The logger object is created using the getLogger function from the logging module, and the name of the logger is set to the name of the current module.

  3. The monkey patch is defined using the monkey function. The monkey patch modifies the behavior of the DeferredAttribute class by replacing the get method with a new implementation.

  4. The new implementation of the get method checks whether the attribute has already been loaded from the database. If it has, the value of the attribute is returned. If it has not, a warning message is logged using the logger object.

  5. The warning message includes the name of the attribute that has not yet been loaded, as well as the name of the model class that the attribute belongs to. The message also includes a note about the potential performance problems associated with lazy loading.

  6. Finally, the monkey patch returns the value of the attribute as usual, using the original implementation of the get method.

Overall, this monkey patch is a useful tool for identifying potential performance problems in Django applications. By logging warning messages when attributes are accessed that have not yet been loaded from the database, it can help developers to identify and address issues that could lead to performance problems down the line.


> Modify that in ways:

  • do not explain what monkey patching is
  • name a problem that are solved here
  • be a lot more concise when explaining the implementation

This code snippet modifies the behavior of the DeferredAttribute class in Django to log a warning message whenever an attribute is accessed that has not yet been loaded from the database. This addresses the potential problem of lazy loading causing performance issues in Django applications.

The code achieves this by replacing the get method of the DeferredAttribute class with a new implementation that checks whether the attribute has already been loaded from the database. If it has not, a warning message is logged that includes the name of the attribute and the name of the model class it belongs to. The warning message also notes the potential performance problems associated with lazy loading. The implementation uses the logging module to create a logger object and the funcy module to apply the monkey patch.


> Add a smart preambule


As web applications grow in complexity, it becomes increasingly important to ensure that they are performant and scalable. One common issue that can arise in Django applications is lazy loading, which can lead to performance problems as the application scales. The code snippet presented here is a clever solution to this problem, demonstrating a technique called monkey patching to modify the behavior of the DeferredAttribute class in Django. By logging a warning message whenever an attribute is accessed that has not yet been loaded from the database, this code addresses the potential performance problems associated with lazy loading in Django applications. In this blog post, we will take a closer look at how the code works and discuss its implications for Django developers.


> Mention 1+N issue specificially


One specific issue that can arise due to lazy loading in Django applications is the 1+N query problem. This occurs when a query is executed to load a parent object, and then additional queries are executed to load related child objects. For example, if a query is executed to load a list of blog posts, and then additional queries are executed to load the comments for each blog post, this can result in a large number of database queries being executed. The 1+N query problem can lead to poor application performance and should be avoided whenever possible. The code snippet presented here addresses this issue by logging a warning message when an attribute is accessed that has not yet been loaded from the database, giving developers the opportunity to optimize their queries and avoid the 1+N query problem.


> Now compile the complete blog post, which would follow all the aforementioned guides and include the code snippet into it


As web applications grow in complexity, it becomes increasingly important to ensure that they are performant and scalable. One common issue that can arise in Django applications is lazy loading, which can lead to performance problems as the application scales. The code snippet presented here is a clever solution to this problem, demonstrating a technique called monkey patching to modify the behavior of the DeferredAttribute class in Django. By logging a warning message whenever an attribute is accessed that has not yet been loaded from the database, this code addresses the potential performance problems associated with lazy loading in Django applications. In this blog post, we will take a closer look at how the code works and discuss its implications for Django developers.

The 1+N Query Problem

One specific issue that can arise due to lazy loading in Django applications is the 1+N query problem. This occurs when a query is executed to load a parent object, and then additional queries are executed to load related child objects. For example, if a query is executed to load a list of blog posts, and then additional queries are executed to load the comments for each blog post, this can result in a large number of database queries being executed. The 1+N query problem can lead to poor application performance and should be avoided whenever possible.

The Solution

The code snippet presented here modifies the behavior of the DeferredAttribute class in Django to log a warning message whenever an attribute is accessed that has not yet been loaded from the database. This helps address the potential problem of lazy loading causing performance issues in Django applications.

Copy code
import logging
import os

from funcy import monkey

from django.db.models.query_utils import DeferredAttribute


logger = logging.getLogger(__name__)


@monkey(DeferredAttribute, "__get__")
def _DeferredAttribute_get(self, instance, cls=None):
    if instance is None:
        return self
    data = instance.__dict__
    field_name = self.field.attname

    # Normally this accessor won't be called if field_name is in __dict__,
    # we need this part so that DeferredAttribute descendants with __set__ play nice.
    if field_name in data:
        return data[field_name]

    # If it's not there already then prevent an SQL query or at least notify we are doing smth bad
    attr = f"{instance.__class__.__name__}.{field_name}"
    # Only trigger this check once per attr to not flood Sentry with identical messages
    if not _seen(attr):
        message = f"Lazy fetching of {attr} may cause 1+N issue"
        # We stop in DEBUG mode and if inside tests but let production to proceed.
        # Using LookupError instead of AttributeError here to prevent higher level "handling" this.
        if os.environ["DEBUG"] == "True" or "PYTEST_CURRENT_TEST" in os.environ:
            raise LookupError(message)
        else:
            logger.exception(message)

    # Proceed normally
    return _DeferredAttribute_get.original(self, instance, cls)


def _seen(key, _memory=set()):
    seen = key in _memory
    _memory.add(key)
    return seen

The code achieves this by replacing the __get__ method of the DeferredAttribute class with a new implementation that checks whether the attribute has already been loaded from the database. If it has not, a warning message is logged that includes the name of the attribute and the name of the model class it belongs to. The warning message also notes the potential performance problems associated with lazy loading. The implementation uses the logging module to create a logger object and the funcy module to apply the monkey patch.

Conclusion

By addressing the potential performance problems associated with lazy loading in Django applications, the code snippet presented here provides a useful tool for Django developers looking to


> Ok, that's great. Please finish the conclusion.


In conclusion, the code trick we discussed today demonstrates a practical use of monkey patching to solve a common issue that developers may encounter when working with Django's DeferredAttribute. By using this code, we can easily identify cases where DeferredAttribute is lazy fetching data, which can lead to the 1+N issue, and take the necessary steps to prevent it. While monkey patching can be a powerful tool in the right hands, it is important to use it judiciously and only when necessary, as it can make code harder to understand and maintain. Nevertheless, in some cases, like the one discussed here, it can be an elegant solution to a difficult problem.


> One more thing. Can you strip down the code sample for demonstration purposes:

  • remove comments
  • always raise, do not log
  • remove any other non-essential parts

Certainly! Here's a stripped-down version of the code snippet:

from funcy import monkey
from django.db.models.query_utils import DeferredAttribute


@monkey(DeferredAttribute, "__get__")
def _DeferredAttribute_get(self, instance, cls=None):
    if instance is None:
        return self
    data = instance.__dict__
    field_name = self.field.attname
    if field_name in data:
        return data[field_name]

    # Raise an exception to prevent an SQL query
    attr = f"{instance.__class__.__name__}.{field_name}"
    message = f"Lazy fetching of {attr} may cause 1+N issue"
    raise LookupError(message)

This code performs the same essential function as the original snippet, but without comments or additional logging. When called, it checks whether a given DeferredAttribute has already been evaluated and, if not, raises an exception to prevent unnecessary SQL queries that could lead to the 1+N issue.


> Cool. Also, may you rewrite this without using funcy?


Sure! Here's a version of the code without using the funcy library:

from django.db.models.query_utils import DeferredAttribute


def _DeferredAttribute_get(self, instance, cls=None):
    if instance is None:
        return self
    data = instance.__dict__
    field_name = self.field.attname
    if field_name in data:
        return data[field_name]

    # Raise an exception to prevent an SQL query
    attr = f"{instance.__class__.__name__}.{field_name}"
    message = f"Lazy fetching of {attr} may cause 1+N issue"
    raise LookupError(message)


DeferredAttribute.__get__ = _DeferredAttribute_get

This version achieves the same functionality as the previous version by directly assigning the _DeferredAttribute_get function to the __get__ attribute of the DeferredAttribute class.


> Finally, come up with a smart-ass title for the blog post


Sure, how about "Monkey Patching Django's DeferredAttribute to Prevent 1+N Queries"?


> That's too direct and not smart-ass at all


Alright, how about "Hacking the Hackers: Preventing Django's 1+N Query Problem with Monkey Patching"?


> Maybe something more concise?


Sure, how about "Defeating Django's 1+N Query Monster with Monkey Patching"?


> Generate ten more different variants for the title


Sure, here are ten more different variants for the title:

  1. Squashing the 1+N Query Bug in Django with Monkey Patching
  2. Taming Django's 1+N Query Problem with a Simple Monkey Patch
  3. Taking Down Django's 1+N Query Dragon with this Simple Trick
  4. Monkey Patching Django: A Smart Solution to 1+N Query Woes
  5. Eliminating Django's 1+N Query Problem with this Clever Hack
  6. Django Gotchas: How to Prevent the 1+N Query Issue with Monkey Patching
  7. Busting the 1+N Query Myth in Django with a Crafty Monkey Patch
  8. Resolving Django's 1+N Query Conundrum with an Elegant Hack
  9. Django Nightmares: The 1+N Query Problem and How to Fix It with Monkey Patching
  10. The Quick Fix: How to Avoid Django's 1+N Query Issue with a Simple Hack

> Ok, thanks


You're welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment