3
0
Fork 0

fix: add make_proxy() convenience method for data model Base

This commit is contained in:
Lance Edgar 2025-01-25 16:26:14 -06:00
parent 2289928337
commit 4a6897c6de
2 changed files with 109 additions and 2 deletions

View file

@ -25,19 +25,103 @@ Base Models
.. class:: Base
This is the base class for all data models.
This is the base class for all :term:`data models <data model>` in
the :term:`app database`. You should inherit from this class when
defining custom models.
This class inherits from :class:`WuttaModelBase`.
"""
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.associationproxy import association_proxy
from wuttjamaican.db.util import (naming_convention, ModelBase,
uuid_column, uuid_fk_column)
class WuttaModelBase(ModelBase):
"""
Base class for data models, from which :class:`Base` inherits.
Custom models should inherit from :class:`Base` instead of this
class.
"""
@classmethod
def make_proxy(cls, main_class, extension, name, proxy_name=None):
"""
Convenience method to declare an "association proxy" for the
main class, per the params.
For more info see
:doc:`sqlalchemy:orm/extensions/associationproxy`.
:param main_class: Reference to the "parent" model class, upon
which the proxy will be defined.
:param extension: Attribute name on the main class, which
references the extension record.
:param name: Attribute name on the extension class, which
provides the proxied value.
:param proxy_name: Optional attribute name on the main class,
which will reference the proxy. If not specified, ``name``
will be used.
As a simple example consider this model, which extends the
:class:`~wuttjamaican.db.model.auth.User` class. In
particular note the last line which is what we're documenting
here::
import sqlalchemy as sa
from sqlalchemy import orm
from wuttjamaican.db import model
class PoserUser(model.Base):
\""" Poser extension for User \"""
__tablename__ = 'poser_user'
uuid = model.uuid_column(sa.ForeignKey('user.uuid'), default=None)
user = orm.relationship(
model.User,
doc="Reference to the main User record.",
backref=orm.backref(
'_poser',
uselist=False,
cascade='all, delete-orphan',
doc="Reference to the Poser extension record."))
favorite_color = sa.Column(sa.String(length=100), nullable=False, doc=\"""
User's favorite color.
\""")
def __str__(self):
return str(self.user)
# nb. this is the method call
PoserUser.make_proxy(model.User, '_poser', 'favorite_color')
That code defines a ``PoserUser`` model but also defines a
``favorite_color`` attribute on the main ``User`` class, such
that it can be used normally::
user = model.User(username='barney', favorite_color='green')
session.add(user)
user = session.query(model.User).filter_by(username='bambam').one()
print(user.favorite_color)
"""
proxy = association_proxy(
extension, proxy_name or name,
creator=lambda value: cls(**{name: value}))
setattr(main_class, name, proxy)
metadata = sa.MetaData(naming_convention=naming_convention)
Base = orm.declarative_base(metadata=metadata, cls=ModelBase)
Base = orm.declarative_base(metadata=metadata, cls=WuttaModelBase)
class Setting(Base):

View file

@ -3,12 +3,34 @@
from unittest import TestCase
try:
import sqlalchemy as sa
from sqlalchemy import orm
from wuttjamaican.db.model import base as mod
from wuttjamaican.db.model.auth import User
except ImportError:
pass
else:
class MockUser(mod.Base):
__tablename__ = 'mock_user'
uuid = mod.uuid_column(sa.ForeignKey('user.uuid'), default=False)
user = orm.relationship(
User,
backref=orm.backref('_mock', uselist=False, cascade='all, delete-orphan'))
favorite_color = sa.Column(sa.String(length=100), nullable=False)
class TestWuttaModelBase(TestCase):
def test_make_proxy(self):
self.assertFalse(hasattr(User, 'favorite_color'))
MockUser.make_proxy(User, '_mock', 'favorite_color')
self.assertTrue(hasattr(User, 'favorite_color'))
user = User(favorite_color='green')
self.assertEqual(user.favorite_color, 'green')
class TestSetting(TestCase):
def test_basic(self):
@ -17,6 +39,7 @@ else:
setting.name = 'foo'
self.assertEqual(str(setting), "foo")
class TestPerson(TestCase):
def test_basic(self):