Compare commits
	
		
			No commits in common. "cb147c203db200136010cd1d96b027fa48ea3d97" and "e9507fb5a468a66f44fa0564dc5329aa299096cc" have entirely different histories.
		
	
	
		
			cb147c203d
			...
			e9507fb5a4
		
	
		
					 9 changed files with 83 additions and 141 deletions
				
			
		|  | @ -5,12 +5,6 @@ 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 | ||||||
|  |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| 
 |  | ||||||
| ``wuttjamaican.db.util`` |  | ||||||
| ======================== |  | ||||||
| 
 |  | ||||||
| .. automodule:: wuttjamaican.db.util |  | ||||||
|    :members: |  | ||||||
|  | @ -17,7 +17,6 @@ | ||||||
|    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.3" | version = "0.13.2" | ||||||
| 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,6 +27,8 @@ 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` | ||||||
|  | @ -37,9 +39,6 @@ The ``wuttjamaican.db.model`` namespace contains the following: | ||||||
| * :class:`~wuttjamaican.db.model.upgrades.Upgrade` | * :class:`~wuttjamaican.db.model.upgrades.Upgrade` | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| # TODO: remove these | from .base import uuid_column, uuid_fk_column, Base, Setting, Person | ||||||
| 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,15 +31,62 @@ Base Models | ||||||
| import sqlalchemy as sa | import sqlalchemy as sa | ||||||
| from sqlalchemy import orm | from sqlalchemy import orm | ||||||
| 
 | 
 | ||||||
| from wuttjamaican.db.util import (naming_convention, ModelBase, | from wuttjamaican.util import make_uuid | ||||||
|                                   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`. | ||||||
|  |  | ||||||
|  | @ -1,77 +0,0 @@ | ||||||
| # -*- 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,16 +3,42 @@ | ||||||
| from unittest import TestCase | from unittest import TestCase | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|     from wuttjamaican.db.model import base as mod |     import sqlalchemy as sa | ||||||
|  |     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 = mod.Setting() |             setting = model.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") | ||||||
|  | @ -20,13 +46,13 @@ else: | ||||||
|     class TestPerson(TestCase): |     class TestPerson(TestCase): | ||||||
| 
 | 
 | ||||||
|         def test_basic(self): |         def test_basic(self): | ||||||
|             person = mod.Person() |             person = model.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 = mod.Person() |             person = model.Person() | ||||||
|             self.assertIsNone(person.user) |             self.assertIsNone(person.user) | ||||||
| 
 | 
 | ||||||
|             user = User() |             user = User() | ||||||
|  |  | ||||||
|  | @ -1,40 +0,0 @@ | ||||||
| # -*- 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue