Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support serializable toolbar #1432

Closed

Conversation

tim-schilling
Copy link
Member

@tim-schilling tim-schilling commented Jan 3, 2021

In order to support Channels' non-django request possibilities, we need to start making the toolbar more flexible.In order to support Django Channels out of the box we need to support multiple processes running. This implies serializing the toolbar's data into some type of backend (database, memory, redis, etc). Currently the toolbar stores everything in memory as non-primitives.

My assumption is that we need to serialize the panel's template context into primitives so that it can be stored in any medium between requests and then rendered at some future time (or even stored elsewhere). I could be wrong here, so feel free to push back. I suspect this is going to change the toolbar's internals dramatically.

This should not be merged as it's not complete, but I wanted to get it out there for feedback and to solicit for ideas/alternative options. Currently this PR does the following:

  • Creates a global store for the toolbar and the panels' stats (debug_toolbar.store.MemoryStore)
  • Reworks all the existing panels to function with their context being serialized to JSON and back (this should be pulled out for the MemoryStore, but I'm doing it to prove that it does work).

Future work:

  • Create LocMemCacheStore DBCacheStore
  • Verify compatibility with Django Channels sync Django views
  • Determine what's needed to support async Django Channels views.

This relates to #1430 and I hope we can structure it in a way such that it also works for async views in Channels and #1413.

@codecov
Copy link

codecov bot commented Jan 3, 2021

Codecov Report

Merging #1432 (3fb2745) into main (e20ac73) will decrease coverage by 0.80%.
The diff coverage is 75.94%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1432      +/-   ##
==========================================
- Coverage   85.73%   84.93%   -0.81%     
==========================================
  Files          35       38       +3     
  Lines        1893     2004     +111     
  Branches      273      280       +7     
==========================================
+ Hits         1623     1702      +79     
- Misses        190      219      +29     
- Partials       80       83       +3     
Impacted Files Coverage Δ
debug_toolbar/db_store.py 0.00% <0.00%> (ø)
debug_toolbar/panels/timer.py 78.94% <0.00%> (-0.72%) ⬇️
debug_toolbar/settings.py 100.00% <ø> (ø)
debug_toolbar/panels/templates/panel.py 87.50% <60.00%> (+0.40%) ⬆️
debug_toolbar/toolbar.py 92.59% <85.71%> (+0.54%) ⬆️
debug_toolbar/store.py 87.09% <87.09%> (ø)
debug_toolbar/panels/history/panel.py 89.09% <87.50%> (-1.30%) ⬇️
debug_toolbar/models.py 100.00% <100.00%> (ø)
debug_toolbar/panels/__init__.py 96.00% <100.00%> (+0.61%) ⬆️
debug_toolbar/panels/cache.py 72.72% <100.00%> (+0.16%) ⬆️
... and 11 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e20ac73...3fb2745. Read the comment docs.

Base automatically changed from master to main February 11, 2021 15:01
In order to support the toolbar persisting stats, we need to move away
from calling record_stats with non-primitives.
Removes storing the entire toolbar in the Store.
@tim-schilling
Copy link
Member Author

@tapaswenipathak tagging you here. Feel free to create a new PR or continue working off of this one.


Provided to support future store mechanisms overriding a panel's content.
"""
return stats

Choose a reason for hiding this comment

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

are these two methods complete?

  1. deserialize_stats
  2. serialize_stats


def deserialize(data):
return json.loads(data)

Choose a reason for hiding this comment

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

are these two methods complete?

  1. serialize()
  2. deserialize()

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.


@classmethod
def delete(cls, store_id):
raise NotImplementedError

Choose a reason for hiding this comment

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

should these four be implemented?

  1. ids()
  2. exists()
  3. set()
  4. delete()

Copy link
Member Author

Choose a reason for hiding this comment

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

Not here. BaseStore is the interface for the API. The subclasses of BaseStore will have different implementations.

except KeyError:
data = None
return {} if data is None else deserialize(data)

Choose a reason for hiding this comment

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

are these two method definitions complete?

  1. save_panel()
  2. panel()

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes

@@ -43,7 +43,7 @@
</td>
<td class="djdt-actions">
<form method="get" action="{% url 'djdt:history_sidebar' %}">
{{ store_context.form }}
{{ history.form }}

Choose a reason for hiding this comment

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

store_context is functionally history after this PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, this is likely due to the context for the template changing.

@classmethod
def fetch(cls, store_id):
return cls._store.get(store_id)
get_store().set(self.store_id)

Choose a reason for hiding this comment

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

now i would:

return import_string(dt_settings.get_config()["TOOLBAR_STORE_CLASS"])

are these edits/changes functional and complete?

Copy link
Member Author

Choose a reason for hiding this comment

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

The function store here is more the verb case of "store". What's happening is that we want to store this version of the toolbar into the storage mechanism. The naming could be improved.

@tim-schilling
Copy link
Member Author

This is going to be replaced by the work associated with https://github.com/orgs/jazzband/projects/9/

@matthiask can you review those high level tasks and let me know what context I'm missing? When these parts are picked up, they should be converted to issues. Though perhaps I should do that now?

@matthiask
Copy link
Member

@tim-schilling Thanks for starting this!

I think we should check if we can use the cache framework instead of writing our own store classes; we should document that you're supposed to use a cache which supports cross-process caching and maybe warn if users use e.g. the local memory or dummy cache backend.

I'm sure that the point could be made that this data doesn't belong in a cache but it would mean much less work for us and many many options immediately available for our users.

I also think Django Channels is nice to have, not essential -- Django itself comes with async views, middleware and ORM methods. I have used Channels in the past with success and I like it, but supporting Django's own async machinery is surely more important.

Re. the serialization framework: Do you think it would be feasible to enforce usage of these methods so that we do not have to support two code paths in the future? I think it would be great if we also added a pending deprecation warning to using current-style panels which do not use serialization at all.

(I haven't thought long and hard about these points so I'm not all that sure that we should do what I'm proposing.)

@tim-schilling
Copy link
Member Author

I think we should check if we can use the cache framework instead of writing our own store classes; we should document that you're supposed to use a cache which supports cross-process caching and maybe warn if users use e.g. the local memory or dummy cache backend

@matthiask Good point. I'm not sure why I went into specific backends. In reality, it should have been MemoryStore, CacheStore and DatabaseStore. However, I'm happy to start with using the cache, then extending if folks want to.

I also think Django Channels is nice to have, not essential

Another good point. But let's reach for it still. Especially if we get help from GSoC.

Re. the serialization framework: Do you think it would be feasible to enforce usage of these methods so that we do not have to support two code paths in the future? I think it would be great if we also added a pending deprecation warning to using current-style panels which do not use serialization at all.

Yes, I think we'll drop the double code paths in the future. I don't have a timeline for you, but if you feel we should define one, I'll say 1 year from release we'll drop the deprecated path.

@matthiask
Copy link
Member

Another good point. But let's reach for it still. Especially if we get help from GSoC.

I totally agree.

Yes, I think we'll drop the double code paths in the future. I don't have a timeline for you, but if you feel we should define one, I'll say 1 year from release we'll drop the deprecated path.

I don't think we need a timeline for this, but we should be intentional about it, and communicate clearly that we don't expect to keep both paths indefinitely.

Thanks!

@matthiask matthiask mentioned this pull request Apr 14, 2023
2 tasks
@tim-schilling
Copy link
Member Author

I'm closing this as this approach is largely being dropped. It can still be referred to for inspiration, but it won't be merged in.

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.

3 participants