From 4ecb5f1de34b68fc73931bcde891f474fa0a1b39 Mon Sep 17 00:00:00 2001 From: ksmuczynski Date: Mon, 15 Sep 2025 20:45:16 -0600 Subject: [PATCH 1/3] feat: add documentation summarizing the purpose and contents of the db/base.py file and its mixins. feat: add StatusHistory model to log all time-variant operational statuses. --- db/base.py | 41 +++++++++++++++++++++++++++++++++++++++-- db/status_history.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 db/status_history.py diff --git a/db/base.py b/db/base.py index fe17e7905..6e72886e7 100644 --- a/db/base.py +++ b/db/base.py @@ -13,6 +13,29 @@ # See the License for the specific language governing permissions and # limitations under the License. # =============================================================================== +""" +db/base.py + +This file defines the foundational components for the SQLAlchemy models. +It includes: +1. The declarative base class (`Base`) that all models will inherit from. +2. A helper function (`lexicon_term`) to create standardized foreign key columns + referencing the `lexicon_term` table. +3. A helper function (`pascal_to_snake`) to convert class names from PascalCase to snake_case + for automatic table naming. +4. Mixins for common functionality: + - `AutoBaseMixin`: Adds automatic table naming and an auto-incrementing primary key. + - `PropertiesMixin`: Adds a JSONB properties column for storing additional attributes. + - `ReleaseMixin`: Adds a release status column referencing the `lexicon_term` table. + - `AuditMixin`: Adds standard audit columns (created_at, created_by, updated_at, updated_by). +5. A simple `User` model for tracking user information in audit columns. +6. Polymorphic helper mixins (`HasStatusHistory`, `HasNotes`, `HasAttribution`, etc.) + which provide a clean, reusable way to add relationships to the polymorphic + metadata tables. Any model that can have a status history (like Thing or Location) + can simply inherit from the `HasStatusHistory` mixin. +7. An `AuditMixin` to add standard audit columns to tables. +""" + from sqlalchemy import ( Column, DateTime, @@ -20,8 +43,6 @@ Integer, JSON, String, - Boolean, - Text, ForeignKey, ) from sqlalchemy.orm import DeclarativeBase, declared_attr, Mapped, mapped_column @@ -41,6 +62,12 @@ class Base(DeclarativeBase): def lexicon_term(foreignkeykw=None, **kw): + """Create a SQLAlchemy mapped column for a self-referencing lexicon term. + + This helper function simplifies the creation of a string column that also + acts as a foreign key to the 'term' column of the 'lexicon_term' table. + It standardizes the column type to String(100) and sets the onupdate + behavior to "CASCADE".""" fkw = foreignkeykw if foreignkeykw else {} @@ -56,12 +83,16 @@ def pascal_to_snake(name): class ReleaseMixin: + """Mixin to add release status to a model.""" + @declared_attr def release_status(self): return lexicon_term(default="draft") class AuditMixin: + """Mixin to add standard audit columns to a model.""" + @declared_attr def created_at(self): return Column( @@ -109,6 +140,8 @@ def updated_by_id(self): class AutoBaseMixin(AuditMixin): + """Mixin to add automatic table naming and an auto-incrementing primary key.""" + @declared_attr def __tablename__(self): return pascal_to_snake(self.__name__) @@ -119,6 +152,8 @@ def id(self): class PropertiesMixin: + """Mixin to add a JSONB properties column for storing additional attributes.""" + @declared_attr def properties(self): return Column( @@ -130,6 +165,8 @@ def properties(self): class User(Base): + """Represents a user in the system.""" + __tablename__ = "user" id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False) diff --git a/db/status_history.py b/db/status_history.py new file mode 100644 index 000000000..0fb18ae08 --- /dev/null +++ b/db/status_history.py @@ -0,0 +1,38 @@ +""" +models/status_history.py + +This model defines the `StatusHistory` table, a central, polymorphic log for +all time-variant operational statuses (e.g., Use Status, Access Status). + +**NOTE**: This is a polymorphic table. It does not define outgoing relationships +itself. Instead, other tables (like Thing and Location) use the `HasStatusHistory` +mixin to establish a One-to-Many relationship TO this table. +""" + +import datetime + +from sqlalchemy import ( + Integer, + String, + DateTime, + Text, +) +from sqlalchemy.orm import Mapped, mapped_column + +from db.base import Base, AutoBaseMixin, ReleaseMixin + + +class StatusHistory(Base, AutoBaseMixin, ReleaseMixin): + status_type: Mapped[str] = mapped_column(String(50), nullable=False) + status_value: Mapped[str] = mapped_column(String(50), nullable=False) + start_date: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), nullable=True + ) + end_date: Mapped[datetime.datetime] = mapped_column( + DateTime(timezone=True), nullable=True + ) + reason: Mapped[str] = mapped_column(Text, nullable=True) + + # Polymorphic relationship columns + statusable_id: Mapped[int] = mapped_column(Integer, nullable=False) + statusable_type: Mapped[str] = mapped_column(String(50), nullable=False) From b08461a4b6674ee6136a460abb3fe4abb7b07f27 Mon Sep 17 00:00:00 2001 From: ksmuczynski Date: Mon, 15 Sep 2025 21:56:19 -0600 Subject: [PATCH 2/3] feat: add polymorphic helper mixin HasStatusHistory --- db/base.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/db/base.py b/db/base.py index 6e72886e7..f7c0e6e88 100644 --- a/db/base.py +++ b/db/base.py @@ -45,7 +45,13 @@ String, ForeignKey, ) -from sqlalchemy.orm import DeclarativeBase, declared_attr, Mapped, mapped_column +from sqlalchemy.orm import ( + DeclarativeBase, + declared_attr, + Mapped, + mapped_column, + relationship, +) from sqlalchemy_searchable import make_searchable from sqlalchemy_continuum import make_versioned import re @@ -82,6 +88,7 @@ def pascal_to_snake(name): return re.sub(r"(? Date: Wed, 17 Sep 2025 10:29:18 -0600 Subject: [PATCH 3/3] refactor: update mixin name from `HasStatusHistory` to `StatusHistoryMixin` --- db/base.py | 6 +++--- db/status_history.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/db/base.py b/db/base.py index f7c0e6e88..4da639a20 100644 --- a/db/base.py +++ b/db/base.py @@ -29,10 +29,10 @@ - `ReleaseMixin`: Adds a release status column referencing the `lexicon_term` table. - `AuditMixin`: Adds standard audit columns (created_at, created_by, updated_at, updated_by). 5. A simple `User` model for tracking user information in audit columns. -6. Polymorphic helper mixins (`HasStatusHistory`, `HasNotes`, `HasAttribution`, etc.) +6. Polymorphic helper mixins (`StatusHistoryMixin`, `NotesMixin`, `AttributionMixin`, etc.) which provide a clean, reusable way to add relationships to the polymorphic metadata tables. Any model that can have a status history (like Thing or Location) - can simply inherit from the `HasStatusHistory` mixin. + can simply inherit from the `StatusHistoryMixin` mixin. 7. An `AuditMixin` to add standard audit columns to tables. """ @@ -172,7 +172,7 @@ def properties(self): # ============= Polymorphic Helper Mixins ============================================= -class HasStatusHistory: +class StatusHistoryMixin: """ Mixin for models that can have a status history (e.g., Thing, Location). It automatically creates a polymorphic One-to-Many relationship to the diff --git a/db/status_history.py b/db/status_history.py index 0fb18ae08..acfd20f5d 100644 --- a/db/status_history.py +++ b/db/status_history.py @@ -5,7 +5,7 @@ all time-variant operational statuses (e.g., Use Status, Access Status). **NOTE**: This is a polymorphic table. It does not define outgoing relationships -itself. Instead, other tables (like Thing and Location) use the `HasStatusHistory` +itself. Instead, other tables (like Thing and Location) use the `StatusHistoryMixin` mixin to establish a One-to-Many relationship TO this table. """