On Fri, Jul 3, 2020, at 9:21 AM, Pedro Ferreira wrote:
> Hi,
> 
> I was wondering if there's some way to create some sort of a "virtual"
> model which is not based on an actual table?
> 
> Use case: I have a model (e.g. `Room`) which contains a `data` JSONB
> attribute/column which is supposed to store plugin-specific data. I
> would like plugin code to be able to create a "virtual" model which
> references `Room` 1-to-1 and injects a `plugin_data` backref into it.
> That means I would be able to do `room.plugin_data.user`, for instance,
> which would use `room.data['user_id']` to fetch a `User` object.
> 
> Right now, our plugins do this by means of an additional table. E.g., in
> this case we would have `PluginRoom`, which would store `user_id` and
> have a 1-to-1 relationship with `Room`, with a backref.
> 
> I was wondering if there's some way we can have a similar pattern based
> on unstructured JSONB data.

Python can do this using patterns like the `__getattr__` method. here's a 30 
second proof of concept. note the use of flag_modified() to ensure that an 
attribute set is flagged for unit of work update.

from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import Integer
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
from sqlalchemy.orm.attributes import flag_modified

Base = declarative_base()


class A(Base):
 __tablename__ = "a"

 id = Column(Integer, primary_key=True)
 _data = Column(JSONB, default={})

 def __init__(self, **kw):
 self._data = {}
 self._data.update(kw)

 def __getattr__(self, key):
 if key.startswith("_"):
 raise AttributeError(key)

 return self._data[key]

 def __setattr__(self, key, value):
 if key.startswith("_"):
 object.__setattr__(self, key, value)
 else:
 self._data[key] = value
 flag_modified(self, "_data")


e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
Base.metadata.drop_all(e)
Base.metadata.create_all(e)

s = Session(e)

a1 = A(foo="bar", bar="bat")

s.add(a1)
s.commit()


assert s.query(A._data).first() == ({"foo": "bar", "bar": "bat"},)

a1.bar = "new bat"

s.commit()

assert s.query(A._data).first() == ({"foo": "bar", "bar": "new bat"},)






> 
> Cheers,
> 
> Pedro
> 
> 
> 
> -- 
> SQLAlchemy - 
> The Python SQL Toolkit and Object Relational Mapper
> 
> http://www.sqlalchemy.org/
> 
> To post example code, please provide an MCVE: Minimal, Complete, and 
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full 
> description.
> --- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to [email protected].
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/5581edb1-3cbe-2179-f925-dbeaaa7523b8%40cern.ch.
> 
> 
> *Attachments:*
>  * signature.asc

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/c7e0b7f2-49fd-4fce-8cd6-b4b4ecca530b%40www.fastmail.com.

Reply via email to