Compare commits
2 commits
e9507fb5a4
...
cb147c203d
Author | SHA1 | Date | |
---|---|---|---|
cb147c203d | |||
4c51189d41 |
|
@ -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
|
||||||
|
|
6
docs/api/wuttjamaican/db.util.rst
Normal file
6
docs/api/wuttjamaican/db.util.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttjamaican.db.util``
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. automodule:: wuttjamaican.db.util
|
||||||
|
:members:
|
|
@ -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
|
||||||
|
|
|
@ -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"}]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
77
src/wuttjamaican/db/util.py
Normal file
77
src/wuttjamaican/db/util.py
Normal 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)
|
|
@ -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
40
tests/db/test_util.py
Normal 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)
|
Loading…
Reference in a new issue