3
0
Fork 0

Compare commits

..

4 commits

Author SHA1 Message Date
69a83ea47f bump: version 0.28.6 → 0.28.7 2026-02-17 14:11:21 -06:00
589f279f04 fix: add assocation proxies for User.first_name and User.last_name
for a rather dubious purpose though..so may need to undo this, we'll see
2026-02-17 14:04:51 -06:00
5ec0a8e82d fix: add get_value() convenience function 2026-02-16 18:11:11 -06:00
fb1a7b22d8 docs: add comment regarding association proxy for dict(obj) 2026-02-15 14:12:37 -06:00
8 changed files with 128 additions and 4 deletions

View file

@ -5,6 +5,15 @@ 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.28.7 (2026-02-17)
### Fix
- add assocation proxies for `User.first_name` and `User.last_name`
- add `get_value()` convenience function
- comment out app_title in default wutta.conf for installer
- add `app.today()` convenience method
## v0.28.6 (2026-01-04) ## v0.28.6 (2026-01-04)
### Fix ### Fix

View file

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

View file

@ -38,6 +38,7 @@ from webhelpers2.html import HTML
from wuttjamaican.util import ( from wuttjamaican.util import (
get_timezone_by_name, get_timezone_by_name,
get_value,
localtime, localtime,
load_entry_points, load_entry_points,
load_object, load_object,
@ -374,6 +375,22 @@ class AppHandler: # pylint: disable=too-many-public-methods
self.__dict__["enum"] = importlib.import_module(spec) self.__dict__["enum"] = importlib.import_module(spec)
return self.enum return self.enum
def get_value(self, obj, key):
"""
Convenience wrapper around
:func:`wuttjamaican.util.get_value()`.
:param obj: Arbitrary dict or object of any kind which would
have named attributes.
:param key: Key/name of the field to get.
:returns: Whatever value is found. Or maybe an
``AttributeError`` is raised if the object does not have
the key/attr set.
"""
return get_value(obj, key)
def load_object(self, spec): def load_object(self, spec):
""" """
Import and/or load and return the object designated by the Import and/or load and return the object designated by the

View file

@ -44,7 +44,7 @@ from sqlalchemy import orm
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
from wuttjamaican.db.util import uuid_column, uuid_fk_column from wuttjamaican.db.util import uuid_column, uuid_fk_column
from wuttjamaican.db.model.base import Base from wuttjamaican.db.model.base import Base, Person
from wuttjamaican.util import make_utc from wuttjamaican.util import make_utc
@ -204,8 +204,6 @@ class User(Base): # pylint: disable=too-few-public-methods
person_uuid = uuid_fk_column("person.uuid", nullable=True) person_uuid = uuid_fk_column("person.uuid", nullable=True)
person = orm.relationship( person = orm.relationship(
"Person", "Person",
# TODO: seems like this is not needed?
# uselist=False,
back_populates="users", back_populates="users",
cascade_backrefs=False, cascade_backrefs=False,
doc=""" doc="""
@ -214,6 +212,21 @@ class User(Base): # pylint: disable=too-few-public-methods
""", """,
) )
# TODO: these may or may not be good ideas? i added them mostly
# for sake of testing association proxy behavior in wuttaweb, b/c
# i was lazy and didn't want to write proper fixtures. so if
# they are a problem then doing that should fix it..
first_name = association_proxy(
"person",
"first_name",
creator=lambda n: Person(first_name=n, full_name=n),
)
last_name = association_proxy(
"person",
"last_name",
creator=lambda n: Person(last_name=n, full_name=n),
)
active = sa.Column( active = sa.Column(
sa.Boolean(), sa.Boolean(),
nullable=False, nullable=False,

View file

@ -56,6 +56,8 @@ class ModelBase: # pylint: disable=empty-docstring
def __iter__(self): def __iter__(self):
# nb. we override this to allow for `dict(self)` # nb. we override this to allow for `dict(self)`
# nb. this does *not* include association proxy values;
# see also wuttjamaican.util.get_value()
state = sa.inspect(self) state = sa.inspect(self)
fields = [attr.key for attr in state.attrs] fields = [attr.key for attr in state.attrs]
return iter([(field, getattr(self, field)) for field in fields]) return iter([(field, getattr(self, field)) for field in fields])

View file

@ -82,6 +82,34 @@ def get_class_hierarchy(klass, topfirst=True):
return hierarchy return hierarchy
def get_value(obj, key):
"""
Convenience function to retrive a value by name from the given
object. This will first try to assume the object is a dict but
will fallback to using ``getattr()`` on it.
:param obj: Arbitrary dict or object of any kind which would have
named attributes.
:param key: Key/name of the field to get.
:returns: Whatever value is found. Or maybe an ``AttributeError``
is raised if the object does not have the key/attr set.
"""
# nb. we try dict access first, since wutta data model objects
# should all support that anyway, so it's 2 birds 1 stone.
try:
return obj[key]
except (KeyError, TypeError):
# nb. key error means the object supports key lookup (i.e. is
# dict-like) but did not have that key set. which is actually
# an expected scenario for association proxy fields, but for
# those a getattr() should still work; see also
# wuttjamaican.db.util.ModelBase
return getattr(obj, key)
def load_entry_points(group, ignore_errors=False): def load_entry_points(group, ignore_errors=False):
""" """
Load a set of ``setuptools``-style entry points. Load a set of ``setuptools``-style entry points.

View file

@ -48,6 +48,32 @@ class TestAppHandler(FileTestCase):
def test_get_enum(self): def test_get_enum(self):
self.assertIs(self.app.get_enum(), wuttjamaican.enum) self.assertIs(self.app.get_enum(), wuttjamaican.enum)
def test_get_value(self):
class Object:
def __init__(self, **kw):
self.__dict__.update(kw)
class Dict(dict):
pass
# dict object
obj = {"foo": "bar"}
self.assertEqual(self.app.get_value(obj, "foo"), "bar")
# object w/ attrs
obj = Object(foo="bar")
self.assertEqual(self.app.get_value(obj, "foo"), "bar")
# dict-like w/ attrs
obj = Dict({"foo": "bar"})
obj.baz = "yyy"
self.assertEqual(self.app.get_value(obj, "baz"), "yyy")
# missing attr
obj = Object(foo="bar")
self.assertRaises(AttributeError, self.app.get_value, obj, "baz")
def test_load_object(self): def test_load_object(self):
# just confirm the method works on a basic level; the # just confirm the method works on a basic level; the

View file

@ -41,6 +41,35 @@ class TestGetClassHierarchy(TestCase):
self.assertEqual(classes, [C, B, A]) self.assertEqual(classes, [C, B, A])
class TestGetValue(TestCase):
def test_basic(self):
class Object:
def __init__(self, **kw):
self.__dict__.update(kw)
class Dict(dict):
pass
# dict object
obj = {"foo": "bar"}
self.assertEqual(mod.get_value(obj, "foo"), "bar")
# object w/ attrs
obj = Object(foo="bar")
self.assertEqual(mod.get_value(obj, "foo"), "bar")
# dict-like w/ attrs
obj = Dict({"foo": "bar"})
obj.baz = "yyy"
self.assertEqual(mod.get_value(obj, "baz"), "yyy")
# missing attr
obj = Object(foo="bar")
self.assertRaises(AttributeError, mod.get_value, obj, "baz")
class TestLoadEntryPoints(TestCase): class TestLoadEntryPoints(TestCase):
def test_empty(self): def test_empty(self):