2
0
Fork 0

Compare commits

...

2 commits

Author SHA1 Message Date
Lance Edgar cb147c203d bump: version 0.13.2 → 0.13.3 2024-08-30 20:34:19 -05:00
Lance Edgar 4c51189d41 fix: move model base class out of model subpkg
and other things, used by other packages (rattail)

otherwise when rattail imports them, the whole WJ model comes along
with it and it can interfere with sqlalchemy-continuum versioning
2024-08-30 20:30:09 -05:00
9 changed files with 141 additions and 83 deletions

View file

@ -5,6 +5,12 @@ All notable changes to WuttJamaican will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.13.3 (2024-08-30)
### Fix
- move model base class out of model subpkg
## v0.13.2 (2024-08-27) ## v0.13.2 (2024-08-27)
### Fix ### Fix

View file

@ -0,0 +1,6 @@
``wuttjamaican.db.util``
========================
.. automodule:: wuttjamaican.db.util
:members:

View file

@ -17,6 +17,7 @@
db.model.base db.model.base
db.model.upgrades db.model.upgrades
db.sess db.sess
db.util
email email
email.handler email.handler
email.message email.message

View file

@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "WuttJamaican" name = "WuttJamaican"
version = "0.13.2" version = "0.13.3"
description = "Base package for Wutta Framework" description = "Base package for Wutta Framework"
readme = "README.md" readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]

View file

@ -27,8 +27,6 @@ This is the default :term:`app model` module.
The ``wuttjamaican.db.model`` namespace contains the following: The ``wuttjamaican.db.model`` namespace contains the following:
* :func:`~wuttjamaican.db.model.base.uuid_column()`
* :func:`~wuttjamaican.db.model.base.uuid_fk_column()`
* :class:`~wuttjamaican.db.model.base.Base` * :class:`~wuttjamaican.db.model.base.Base`
* :class:`~wuttjamaican.db.model.base.Setting` * :class:`~wuttjamaican.db.model.base.Setting`
* :class:`~wuttjamaican.db.model.base.Person` * :class:`~wuttjamaican.db.model.base.Person`
@ -39,6 +37,9 @@ The ``wuttjamaican.db.model`` namespace contains the following:
* :class:`~wuttjamaican.db.model.upgrades.Upgrade` * :class:`~wuttjamaican.db.model.upgrades.Upgrade`
""" """
from .base import uuid_column, uuid_fk_column, Base, Setting, Person # TODO: remove these
from wuttjamaican.db.util import uuid_column, uuid_fk_column
from .base import Base, Setting, Person
from .auth import Role, Permission, User, UserRole from .auth import Role, Permission, User, UserRole
from .upgrades import Upgrade from .upgrades import Upgrade

View file

@ -31,62 +31,15 @@ Base Models
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from wuttjamaican.util import make_uuid from wuttjamaican.db.util import (naming_convention, ModelBase,
uuid_column, uuid_fk_column)
# nb. this convention comes from upstream docs
# https://docs.sqlalchemy.org/en/14/core/constraints.html#constraint-naming-conventions
naming_convention = {
'ix': 'ix_%(column_0_label)s',
'uq': 'uq_%(table_name)s_%(column_0_name)s',
'ck': 'ck_%(table_name)s_%(constraint_name)s',
'fk': 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s',
'pk': 'pk_%(table_name)s',
}
metadata = sa.MetaData(naming_convention=naming_convention) metadata = sa.MetaData(naming_convention=naming_convention)
class ModelBase:
""" """
def __iter__(self):
# nb. we override this to allow for `dict(self)`
state = sa.inspect(self)
fields = [attr.key for attr in state.attrs]
return iter([(field, getattr(self, field))
for field in fields])
def __getitem__(self, key):
# nb. we override this to allow for `x = self['field']`
state = sa.inspect(self)
if hasattr(state.attrs, key):
return getattr(self, key)
Base = orm.declarative_base(metadata=metadata, cls=ModelBase) Base = orm.declarative_base(metadata=metadata, cls=ModelBase)
def uuid_column(*args, **kwargs):
"""
Returns a UUID column for use as a table's primary key.
"""
kwargs.setdefault('primary_key', True)
kwargs.setdefault('nullable', False)
kwargs.setdefault('default', make_uuid)
return sa.Column(sa.String(length=32), *args, **kwargs)
def uuid_fk_column(target_column, *args, **kwargs):
"""
Returns a UUID column for use as a foreign key to another table.
:param target_column: Name of the table column on the remote side,
e.g. ``'user.uuid'``.
"""
return sa.Column(sa.String(length=32), sa.ForeignKey(target_column), *args, **kwargs)
class Setting(Base): class Setting(Base):
""" """
Represents a :term:`config setting`. Represents a :term:`config setting`.

View file

@ -0,0 +1,77 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttJamaican -- Base package for Wutta Framework
# Copyright © 2023-2024 Lance Edgar
#
# This file is part of Wutta Framework.
#
# Wutta Framework is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Wutta Framework is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Database Utilities
"""
import sqlalchemy as sa
from wuttjamaican.util import make_uuid
# nb. this convention comes from upstream docs
# https://docs.sqlalchemy.org/en/14/core/constraints.html#constraint-naming-conventions
naming_convention = {
'ix': 'ix_%(column_0_label)s',
'uq': 'uq_%(table_name)s_%(column_0_name)s',
'ck': 'ck_%(table_name)s_%(constraint_name)s',
'fk': 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s',
'pk': 'pk_%(table_name)s',
}
class ModelBase:
""" """
def __iter__(self):
# nb. we override this to allow for `dict(self)`
state = sa.inspect(self)
fields = [attr.key for attr in state.attrs]
return iter([(field, getattr(self, field))
for field in fields])
def __getitem__(self, key):
# nb. we override this to allow for `x = self['field']`
state = sa.inspect(self)
if hasattr(state.attrs, key):
return getattr(self, key)
def uuid_column(*args, **kwargs):
"""
Returns a UUID column for use as a table's primary key.
"""
kwargs.setdefault('primary_key', True)
kwargs.setdefault('nullable', False)
kwargs.setdefault('default', make_uuid)
return sa.Column(sa.String(length=32), *args, **kwargs)
def uuid_fk_column(target_column, *args, **kwargs):
"""
Returns a UUID column for use as a foreign key to another table.
:param target_column: Name of the table column on the remote side,
e.g. ``'user.uuid'``.
"""
return sa.Column(sa.String(length=32), sa.ForeignKey(target_column), *args, **kwargs)

View file

@ -3,42 +3,16 @@
from unittest import TestCase from unittest import TestCase
try: try:
import sqlalchemy as sa from wuttjamaican.db.model import base as mod
from wuttjamaican.db.model import base as model
from wuttjamaican.db.model.auth import User from wuttjamaican.db.model.auth import User
except ImportError: except ImportError:
pass pass
else: else:
class TestModelBase(TestCase):
def test_dict_behavior(self):
setting = model.Setting()
self.assertEqual(list(iter(setting)), [('name', None), ('value', None)])
self.assertIsNone(setting['name'])
setting.name = 'foo'
self.assertEqual(setting['name'], 'foo')
class TestUUIDColumn(TestCase):
def test_basic(self):
column = model.uuid_column()
self.assertIsInstance(column, sa.Column)
self.assertIsInstance(column.type, sa.String)
self.assertEqual(column.type.length, 32)
class TestUUIDFKColumn(TestCase):
def test_basic(self):
column = model.uuid_fk_column('foo.bar')
self.assertIsInstance(column, sa.Column)
self.assertIsInstance(column.type, sa.String)
self.assertEqual(column.type.length, 32)
class TestSetting(TestCase): class TestSetting(TestCase):
def test_basic(self): def test_basic(self):
setting = model.Setting() setting = mod.Setting()
self.assertEqual(str(setting), "") self.assertEqual(str(setting), "")
setting.name = 'foo' setting.name = 'foo'
self.assertEqual(str(setting), "foo") self.assertEqual(str(setting), "foo")
@ -46,13 +20,13 @@ else:
class TestPerson(TestCase): class TestPerson(TestCase):
def test_basic(self): def test_basic(self):
person = model.Person() person = mod.Person()
self.assertEqual(str(person), "") self.assertEqual(str(person), "")
person.full_name = "Barney Rubble" person.full_name = "Barney Rubble"
self.assertEqual(str(person), "Barney Rubble") self.assertEqual(str(person), "Barney Rubble")
def test_users(self): def test_users(self):
person = model.Person() person = mod.Person()
self.assertIsNone(person.user) self.assertIsNone(person.user)
user = User() user = User()

40
tests/db/test_util.py Normal file
View file

@ -0,0 +1,40 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
try:
import sqlalchemy as sa
from wuttjamaican.db import util as mod
from wuttjamaican.db.model.base import Setting
except ImportError:
pass
else:
class TestModelBase(TestCase):
def test_dict_behavior(self):
setting = Setting()
self.assertEqual(list(iter(setting)), [('name', None), ('value', None)])
self.assertIsNone(setting['name'])
setting.name = 'foo'
self.assertEqual(setting['name'], 'foo')
class TestUUIDColumn(TestCase):
def test_basic(self):
column = mod.uuid_column()
self.assertIsInstance(column, sa.Column)
self.assertIsInstance(column.type, sa.String)
self.assertEqual(column.type.length, 32)
class TestUUIDFKColumn(TestCase):
def test_basic(self):
column = mod.uuid_fk_column('foo.bar')
self.assertIsInstance(column, sa.Column)
self.assertIsInstance(column.type, sa.String)
self.assertEqual(column.type.length, 32)