Browse Source

Use SQLAlchemy object lifecycle event for metrics

The creation of the Theme Previews page showed that the way I was doing
metrics for database model objects being created wasn't very good.
Whenever someone loaded the Theme Previews page, the "total topics"
metric would increase by 2, "total comments" by 4, and "exemplary
labels" by 1, because the page is creating that many fake objects and
the metrics were being sent in each class's __init__ method.

This changes to take advantage of SQLAlchemy's object lifecycle event
for "pending to persistent", which only triggers when an object is
actually persisted to the database. When this event happens, the
object's _update_creation_metric method is called, and all metric
updates have been moved into that method now.
merge-requests/85/head
Deimos 5 years ago
parent
commit
c0d7e38e19
  1. 11
      tildes/tildes/database.py
  2. 1
      tildes/tildes/models/comment/comment.py
  3. 3
      tildes/tildes/models/comment/comment_label.py
  4. 1
      tildes/tildes/models/comment/comment_vote.py
  5. 12
      tildes/tildes/models/database_model.py
  6. 1
      tildes/tildes/models/group/group_subscription.py
  7. 2
      tildes/tildes/models/message/message.py
  8. 7
      tildes/tildes/models/topic/topic.py
  9. 1
      tildes/tildes/models/topic/topic_vote.py

11
tildes/tildes/database.py

@ -7,7 +7,7 @@ from typing import Callable, Type
from pyramid.config import Configurator
from pyramid.request import Request
from sqlalchemy import engine_from_config
from sqlalchemy import engine_from_config, event
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session
from sqlalchemy.pool import NullPool
@ -92,6 +92,15 @@ def includeme(config: Configurator) -> None:
reify=True,
)
# add a listener to the session to update database model creation metrics when
# any object goes through the "pending to persistent" state change
event.listen(
session_factory,
"pending_to_persistent",
# pylint: disable=protected-access
lambda session, instance: instance._update_creation_metric(),
)
config.add_request_method(query_factory, "query")
config.add_request_method(obtain_lock, "obtain_lock")

1
tildes/tildes/models/comment/comment.py

@ -141,6 +141,7 @@ class Comment(DatabaseModel):
self.markdown = markdown
self.parent_comment = parent_comment
def _update_creation_metric(self) -> None:
incr_counter("comments")
def __acl__(self) -> Sequence[Tuple[str, Any, str]]:

3
tildes/tildes/models/comment/comment_label.py

@ -64,7 +64,8 @@ class CommentLabel(DatabaseModel):
self.weight = weight
self.reason = reason
incr_counter("comment_labels", label=label.name)
def _update_creation_metric(self) -> None:
incr_counter("comment_labels", label=self.label.name)
@property
def name(self) -> str:

1
tildes/tildes/models/comment/comment_vote.py

@ -48,4 +48,5 @@ class CommentVote(DatabaseModel):
self.user = user
self.comment = comment
def _update_creation_metric(self) -> None:
incr_counter("votes", target_type="comment")

12
tildes/tildes/models/database_model.py

@ -97,6 +97,18 @@ class DatabaseModelBase:
return utc_now() - self.created_time # type: ignore
def _update_creation_metric(self) -> None:
"""Update the metric tracking creations of this model type.
This function will be attached to the SQLAlchemy Object Lifecycle event for the
"pending to persistent" transition, which occurs when an object is persisted to
the database. This ensures that the metric is only updated when an object is
truly created in the database, not just whenever the model class is initialized.
Model classes that have a creation metric should override this method.
"""
pass
def _validate_new_value(self, attribute: str, value: Any) -> Any:
"""Validate the new value for a column.

1
tildes/tildes/models/group/group_subscription.py

@ -48,4 +48,5 @@ class GroupSubscription(DatabaseModel):
self.user = user
self.group = group
def _update_creation_metric(self) -> None:
incr_counter("subscriptions")

2
tildes/tildes/models/message/message.py

@ -118,6 +118,7 @@ class MessageConversation(DatabaseModel):
self.markdown = markdown
self.rendered_html = convert_markdown_to_safe_html(markdown)
def _update_creation_metric(self) -> None:
incr_counter("messages", type="conversation")
def __acl__(self) -> Sequence[Tuple[str, Any, str]]:
@ -243,6 +244,7 @@ class MessageReply(DatabaseModel):
self.markdown = markdown
self.rendered_html = convert_markdown_to_safe_html(markdown)
def _update_creation_metric(self) -> None:
incr_counter("messages", type="reply")
@property

7
tildes/tildes/models/topic/topic.py

@ -217,8 +217,6 @@ class Topic(DatabaseModel):
new_topic.topic_type = TopicType.TEXT
new_topic.markdown = markdown
incr_counter("topics", type="text")
return new_topic
@classmethod
@ -230,10 +228,11 @@ class Topic(DatabaseModel):
new_topic.topic_type = TopicType.LINK
new_topic.link = link
incr_counter("topics", type="link")
return new_topic
def _update_creation_metric(self) -> None:
incr_counter("topics", type=self.topic_type.name.lower())
def __acl__(self) -> Sequence[Tuple[str, Any, str]]:
"""Pyramid security ACL."""
acl = []

1
tildes/tildes/models/topic/topic_vote.py

@ -48,4 +48,5 @@ class TopicVote(DatabaseModel):
self.user = user
self.topic = topic
def _update_creation_metric(self) -> None:
incr_counter("votes", target_type="topic")
Loading…
Cancel
Save