Compare commits
4 commits
7734ee3b75
...
69a83ea47f
| Author | SHA1 | Date | |
|---|---|---|---|
| 69a83ea47f | |||
| 589f279f04 | |||
| 5ec0a8e82d | |||
| fb1a7b22d8 |
8 changed files with 128 additions and 4 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"}]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue