opinionated-mixins
Opinionated, consensus-driven model mixins — timestamps, feedback, announcements — consistent across Python storage frameworks (ORMs & ODMs).
Why?
Most projects need timestamps, soft-delete, announcements, feedback forms — and each time, you re-invent the field names, the enum values, the defaults. Should it be created_at or date_created? Is the status field called state or status? Is "resolved" a valid status or should it be "closed"?
opinionated-mixins makes these decisions for you, based on what the industry already agreed on.
Think of it like:
- Mobbin — instead of designing UI patterns from scratch, you reference what top apps already do
- shadcn/ui — copy well-designed components into your project and customize from there
- Bootstrap — standardized building blocks so teams stop bikeshedding
Same idea, applied to data models. Use the standard way. Change it when you need it.
How It Works
Every field name, enum value, and default is chosen by researching what popular platforms and frameworks do (GitHub, Zendesk, JIRA, Django packages, etc.), then weighing them with the consensus criteria. Proposals require at least 3 independent real-world references.
The same mixin is implemented across all supported frameworks with identical field names and behavior, adapted to each framework's idioms.
Related Projects and Inspiration
These adjacent projects helped shape the way this library thinks about shared conventions, schemas, and reusable model contracts:
- awesome-json-schema — a curated collection of JSON Schema resources, tools, and examples
- OpenDTO Specification — an open specification for standardized DTO design and interoperability
- Schema.org — a large, established shared vocabulary project for structured data
Example
from opinionated_mixins.contrib.sqlalchemy import Announcement
class MyAnnouncement(Base, Announcement):
__tablename__ = "announcements"
id = Column(Integer, primary_key=True)
# Gets: title (str), content (text), category (enum)
Switch to MongoDB? Same fields, same names:
from opinionated_mixins.contrib.mongoengine import Announcement
class MyAnnouncement(Document, Announcement):
pass
# Same: title, content, category — with MongoEngine field types
Supported Frameworks
| Framework | Type | Use Case |
|---|---|---|
| SQLAlchemy | SQL ORM | Declarative model mixins |
| SQLModel | SQL ORM (Pydantic + SA) | Native Field mixins |
| MongoEngine | MongoDB ODM | Document field mixins |
| ODMantic | MongoDB async ODM | Model field mixins |
Available Mixins
| Mixin | Fields | Shared Enums |
|---|---|---|
| Announcement | title, content, category |
AnnouncementCategory (8 values) |
| Feedback | subject, content, category, status |
FeedbackCategory (4), FeedbackStatus (4) |
| Lead | title, salutation, job_title, company_name, website, linkedin_url, status, source, industry, rating, opportunity_amount, currency, probability, close_date, last_contacted, next_follow_up, description, is_active |
LeadStatus (5), LeadSource (7), LeadRating (3) |
| Person | first_name, last_name, middle_name, phone_number, email, street_address, postal_code, city, country, date_of_birth, bio |
— |
| Template | name, content, format, type |
TemplateFormat (3), TemplateType (4) |
| User | username, hashed_password, email, date_email_verified |
— |
See open proposals for upcoming mixins.
Installation
pip install opinionated-mixins
Zero runtime dependencies. Framework packages (SQLAlchemy, MongoEngine, etc.) are your responsibility — you already have them in your project.
Architecture
opinionated-mixins provides reusable fields for Python storage frameworks. Consumers compose plain mixin classes with their own ORM or ODM base classes.
Framework implementations
- SQLAlchemy supplies declarative mixins with columns.
- SQLModel supplies native
Fielddefinitions, so mixin fields are real Pydantic fields ontable=Truemodels. - MongoEngine supplies document field mixins.
- ODMantic supplies annotated field definitions through a small compatibility metaclass so plain mixin fields become ODMantic model fields when composed with
odmantic.Model.
Layout
src/opinionated_mixins/contrib/<framework>/ contains one file per mixin. Each framework's __init__.py exports its mixins. Shared enums live in src/opinionated_mixins/enums.py, and tests mirror the framework layout.
Available mixins cover people, users, announcements, feedback, leads, templates, activity, notifications, timestamps, identifiers, and active flags. Field names and defaults follow the contracts recorded in the RFC index.
Design and verification
Mixins remain plain classes so consumers choose the framework base and can combine several mixins. Tests check field definitions, consistency across frameworks, and persistence using SQLite or MongoDB mocks. Optional MongoDB integration tests use testcontainers.
The package has no runtime dependencies. Consumers install their framework; contributors install development, test, and style tools with uv sync.
New mixins, fields, framework support, and breaking changes require an accepted RFC. Documentation corrections and examples do not require one.
Development
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install all dependencies
uv sync
# Run tests
uv run pytest tests
# Run tests with coverage
uv run coverage run -m pytest tests
uv run coverage report
# Lint (check / auto-fix)
uv run ruff check .
uv run ruff check --fix .
# Format (check / auto-fix)
uv run ruff format --check .
uv run ruff format .
# Type checking
uv run mypy --install-types --non-interactive src/opinionated_mixins
Roadmap
The RFC index records accepted designs and the status of proposals. Open issues track implementation work.
Priorities
- Expand ODMantic examples now that plain mixin composition is supported.
- Expand working examples and document framework-specific limitations.
- Extend integration coverage for storage behavior and mixin composition.
The library already includes timestamp, active-flag, integer-ID, and UUID-ID mixins. CI runs linting, formatting, type checking, and tests on Python 3.10 and 3.14.
Additional mixins, fields, or storage frameworks require research and an accepted RFC. See CONTRIBUTING.md to propose work.
Proposed additional mixins
The earlier design discussion proposed the following mixins. These are candidates for RFCs; the remaining field names and contracts are provisional.
Issue #115 tracks the partial and unimplemented proposals below, along with framework coverage candidates.
| Proposal | Current coverage | Remaining work |
|---|---|---|
| Timestamp | CreatedAt and UpdatedAt provide created_at and updated_at. |
Implemented as separate composable mixins. |
| UUID | UUIDID provides UUID primary keys for SQLAlchemy and SQLModel. | Implemented for SQL storage; MongoDB models use their native identifiers. |
| SoftDelete | IsActive provides an active flag. | A dedicated deleted_at / deleted_by contract. |
| Audit | Activity records events and their actors. | Record-level created_by / updated_by fields. |
| Address | Person includes street address, city, postal code, and country. | A dedicated address mixin, including additional address lines and state/province. |
| Contact | Person includes email and phone number; Lead includes website and LinkedIn URL. | A dedicated contact mixin and a decision on mobile/fax fields. |
| Status | Feedback and Lead have domain-specific status enums. | A general status mixin with change reason, time, and actor. |
| Slug | No dedicated implementation; design in RFC-0014. | Implement the slug field across adapters per the accepted RFC. |
| Metadata | Activity and Notification include JSON data. |
A general metadata/tags contract. |
| Version | No dedicated implementation. | Version, latest-version flag, and parent reference. |
| Priority | No dedicated implementation. | Priority and ordering fields. |
| Expiration | No dedicated implementation. | Expiration time and computed expiry behavior. |
Framework considerations
Each accepted mixin should provide consistent field names and behavior across applicable supported frameworks, using the framework's native field types, constraints, and validation.
- SQLAlchemy: supported; declarative columns, types, and constraints.
- MongoEngine: supported; document fields and validation.
- ODMantic: supported annotated field definitions with plain mixin composition.
- SQLModel: supported; native
Fielddefinitions (Pydantic fields and SQL columns). - TortoiseORM: proposed expansion; native ORM field definitions and persistence tests.
- Beanie: proposed expansion; document fields compatible with Pydantic validation, with the consumer choosing the document base.
- Pydantic: proposed input-layer expansion; annotated fields and validation, with the consumer choosing the model base.
- Dataclasses: proposed expansion; field annotations and defaults.
- WTForms: proposed input-layer expansion; form fields and validators.
The last five frameworks are roadmap candidates, not current support. Adding them requires accepted framework RFCs; Pydantic, dataclasses, and WTForms also require revisiting the current storage-only scope.
Frequently asked questions
Which frameworks are supported?
SQLAlchemy, SQLModel, MongoEngine, and ODMantic. These are storage frameworks; input validation libraries and form frameworks are outside the current scope. See Architecture for framework limitations.
How do I install the package?
pip install opinionated-mixins
Install your chosen framework separately. The base package has no runtime dependencies and requires Python 3.10 or newer.
How do I use a mixin?
Compose it with your application's framework base:
from opinionated_mixins.contrib.sqlalchemy import Person
from sqlalchemy import Column, Integer
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class Contact(Person, Base):
__tablename__ = "contacts"
id = Column(Integer, primary_key=True)
Contact receives the Person fields, including first_name, last_name, phone_number, street_address, and date_of_birth. You can override columns on the concrete model.
Can ODMantic models inherit fields from these mixins?
Yes. ODMantic mixins use a compatibility metaclass that copies mixin annotations into the concrete model namespace before ODMantic builds the model. The Starlette example still declares fields directly where it intentionally uses datetime.datetime for BSON date storage.
Mixins also compose with odmantic.EmbeddedModel for embedded documents.
How do I query ODMantic date-only fields?
ODMantic does not serialize query operands. The Person.date_of_birth and Lead date fields
are stored as midnight BSON datetimes, so queries must use that same representation:
import datetime
dob = datetime.date(1990, 1, 15)
operand = datetime.datetime.combine(dob, datetime.time())
person = await engine.find_one(MyPerson, MyPerson.date_of_birth == operand)
Use BSON-native operands with comparison methods, membership queries, and standalone
odmantic.query helpers. This also applies to decimal amounts: use bson.Decimal128
when querying Lead.opportunity_amount. Ordinary Pydantic dumps retain date and decimal values.
How do I run the checks?
uv sync
uv run pytest tests
uv run ruff check .
uv run ruff format --check .
uv run mypy src/opinionated_mixins
How do I propose a change or report a bug?
See CONTRIBUTING.md. New mixins, fields, framework support, and breaking changes follow the RFC process. For bugs, use the bug report template and include a minimal reproduction.
Contributing
New mixin ideas? Open a Model Proposal. New fields on existing mixins? Open a Field Proposal.
Both require independent real-world references (see the consensus criteria) — this project runs on consensus, not opinion.
License
opinionated-mixins is distributed under the terms of the MIT license.
Metadata
Release files for opinionated-mixins 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| opinionated_mixins-0.2.0.tar.gz | 64.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| opinionated_mixins-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 111.1 kB
Release files / opinionated_mixins-0.2.0.tar.gz
| Download URL | opinionated_mixins-0.2.0.tar.gz |
|---|---|
| Size | 64.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
20b764f7ba7116d4c5d6990bde14736b9ad3f7bc250bae09be28bb487d0723de
|
|
BLAKE2b-256 checksum How to use checksums |
93c8aae341d8e6efce29f8cde2bd721d06644522ecb0908c1d29a1980efbc1dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / opinionated_mixins-0.2.0-py3-none-any.whl
| Download URL | opinionated_mixins-0.2.0-py3-none-any.whl |
|---|---|
| Size | 47.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b293d9915eb7bed7a2230038fda7eda74d48ce387aaf5e0cedc68e48be05e003
|
|
BLAKE2b-256 checksum How to use checksums |
956f8bc18dbd01c9827f2d88d08f8c280f6eb00fa7bd18651da8b7067aef7ff8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log