From f45e47363def6ae0ad77e8093135a2763faad3f1 Mon Sep 17 00:00:00 2001
From: Fabio Manganiello <fabio@manganiello.tech>
Date: Fri, 10 Mar 2023 12:01:23 +0100
Subject: [PATCH] Use lazy='joined' instead of lazy='selectin' on
 Entity.parent.

That's the best way to ensure that all the columns are fetched eagerly and
prevent errors later when trying to access lazily loaded attributes outside
of the session/thread.
---
 platypush/entities/_base.py | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/platypush/entities/_base.py b/platypush/entities/_base.py
index fc792973..37840b72 100644
--- a/platypush/entities/_base.py
+++ b/platypush/entities/_base.py
@@ -3,10 +3,10 @@ import json
 import pathlib
 import types
 from datetime import datetime
-from dateutil.tz import tzutc
-from typing import Callable, Final, Mapping, Set, Type, Tuple, Any
-
 import pkgutil
+from typing import Callable, Dict, Final, Set, Type, Tuple, Any
+
+from dateutil.tz import tzutc
 from sqlalchemy import (
     Boolean,
     Column,
@@ -24,7 +24,8 @@ from sqlalchemy.orm import ColumnProperty, Mapped, backref, relationship
 from platypush.common.db import Base
 from platypush.message import JSONAble
 
-entities_registry: Mapping[Type['Entity'], Mapping] = {}
+EntityRegistryType = Dict[str, Type['Entity']]
+entities_registry: EntityRegistryType = {}
 
 _import_error_ignored_modules: Final[Set[str]] = {'bluetooth'}
 """
@@ -78,7 +79,7 @@ if 'entity' not in Base.metadata:
             'Entity',
             remote_side=[id],
             uselist=False,
-            lazy='selectin',
+            lazy='joined',
             post_update=True,
             backref=backref(
                 'children',
@@ -104,7 +105,7 @@ if 'entity' not in Base.metadata:
 
         @classmethod  # type: ignore
         @property
-        def columns(cls) -> Tuple[ColumnProperty]:
+        def columns(cls) -> Tuple[ColumnProperty, ...]:
             inspector = schema_inspect(cls)
             return tuple(inspector.mapper.column_attrs)
 
@@ -138,12 +139,21 @@ if 'entity' not in Base.metadata:
             return {col.key: self._serialize_value(col) for col in self.columns}
 
         def __repr__(self):
+            """
+            Same as :meth:`.__str__`.
+            """
             return str(self)
 
         def __str__(self):
+            """
+            :return: A JSON-encoded representation of the entity.
+            """
             return json.dumps(self.to_dict())
 
         def __setattr__(self, key, value):
+            """
+            Serializes the new value before assigning it to an attribute.
+            """
             matching_columns = [c for c in self.columns if c.expression.name == key]
 
             if (
@@ -212,11 +222,17 @@ def _discover_entity_types():
                 entities_registry[obj] = {}
 
 
-def get_entities_registry():
+def get_entities_registry() -> EntityRegistryType:
+    """
+    :returns: A copy of the entities registry.
+    """
     return entities_registry.copy()
 
 
 def init_entities_db():
+    """
+    Initializes the entities database.
+    """
     from platypush.context import get_plugin
 
     _discover_entity_types()