Getting Started with MIXINv2¶
The Step 4 Python code works, but it has a structural problem: business logic
and I/O are tangled together. Consider user_id in HttpHandlers:
def user_id(request: BaseHTTPRequestHandler) -> int:
return int(request.path.split("/")[-1])
This one function mixes three concerns: reading request.path (I/O), splitting
on "/" (a business decision about URL format), and parsing the last segment as
an integer (another business decision). The path separator, SQL queries, and
format templates are all hardcoded in Python — changing any of them means
changing Python code.
MIXINv2 solves this by separating the application into three layers:
Python FFI wraps individual stdlib calls in
@scopeadapters — one class per operation (sqlite3.connect,str.split,wfile.write). Each adapter declares its inputs as@externand exposes a single@public @resourceoutput. The adapter contains zero business logic..mixin.yamlfiles contain all application logic, written in MIXINv2. MIXINv2 is not just a configuration format — it is a complete language with lexical scoping, nested scopes, deep-merge composition, and lazy evaluation. These features make it more natural than Python for expressing business logic, which is inherently declarative (“the user ID is the last URL segment”, “the response format istotal={total} current={current}”).Configuration values (SQL queries, format strings, host/port) are pure YAML scalars, gathered in one place.
Business logic written in .mixin.yaml is portable: it is decoupled from the
Python FFI layer. Swap the FFI adapters and the same .mixin.yaml logic runs against
a different runtime — mock adapters for unit testing, a different language’s
stdlib for cross-platform deployment, or instrumented adapters for profiling.
With Python @scope decorators, business logic is locked to the Python runtime
and cannot be extracted or retargeted.
Below is the same Step 4 web application rewritten in this style.
Python FFI adapters¶
Each @scope class wraps exactly one stdlib operation. Below are three
representative adapters; the full module
(packages/mixinv2-examples/src/mixinv2_examples/app_mixin/StdlibFFI/FFI/)
contains 10 more following the same pattern.
SqliteConnectAndExecuteScript — multiple inputs, single output:
"""sqlite3.connect(databasePath) + executescript(setupSql) -> connection"""
import sqlite3
from mixinv2 import extern, public, resource
@extern
def databasePath() -> str: ...
@extern
def setupSql() -> str: ...
@public
@resource
def connection(databasePath: str, setupSql: str) -> sqlite3.Connection:
conn = sqlite3.connect(databasePath, check_same_thread=False)
conn.executescript(setupSql)
return conn
GetItem — generic sequence[index] operation:
"""sequence[index] -> element"""
from mixinv2 import extern, public, resource
@extern
def sequence() -> object: ...
@extern
def index() -> int: ...
@public
@resource
def element(sequence: object, index: int) -> object:
return sequence[index] # type: ignore[index]
HttpSendResponse — chained I/O, send status + headers + body:
"""send_response(statusCode) + end_headers() + wfile.write(body) -> written"""
from http.server import BaseHTTPRequestHandler
from mixinv2 import extern, public, resource
@extern
def request() -> BaseHTTPRequestHandler: ...
@extern
def statusCode() -> int: ...
@extern
def body() -> bytes: ...
@public
@resource
def written(
request: BaseHTTPRequestHandler, statusCode: int, body: bytes
) -> BaseHTTPRequestHandler:
request.send_response(statusCode)
request.end_headers()
request.wfile.write(body)
return request
Notice what is not here: no SQL queries, no "/" separator, no format string,
no :memory: path, no port number. Those are all business decisions that live
in the .mixin.yaml.
.mixin.yaml composition¶
A .mixin.yaml file describes a dependency graph, not an execution sequence.
There is no top-to-bottom control flow — the runtime evaluates resources lazily,
on demand. Think spreadsheet cells, not shell scripts.
The business logic lives in Library.mixin.yaml, which references FFI adapters
through abstract declarations (FFI: scope with [] slots). A concrete FFI
module (StdlibFFI/FFI/) overrides these slots at composition time. This
separation means the business logic is portable — swap stdlib_ffi for a
different FFI implementation and the .mixin.yaml files need no changes.
The following sections walk through Library.mixin.yaml one scope at a time,
introducing new language concepts as they appear.
SQLiteDatabase — extern, inheritance, wiring, projection¶
SQLiteDatabase:
databasePath: [] # extern: caller must provide this
setupSql: [] # extern: caller must provide this
_db:
- [FFI, SqliteConnectAndExecuteScript] # inherit the FFI adapter
- databasePath: [databasePath] # wire extern → adapter input
setupSql: [setupSql]
connection: [_db, connection] # projection: expose _db.connection
Four new concepts:
field: []— an extern declaration, the.mixin.yamlequivalent of@extern. The value must come from a parent scope or the caller.- [FFI, SqliteConnectAndExecuteScript]— inheritance._dbinherits the FFI adapter, gaining all of its resources (connection).database_path: [database_path]— wiring. The reference[database_path]is a lexical lookup: search outward through enclosing scopes until a field nameddatabase_pathis found.connection: [_db, connection]— path navigation. Access theconnectionresource on the child scope_db. The leading underscore on_dbmakes it private;connectionis the public-facing projection.
UserRepository (app-scoped part) — nested scope, scope-as-dataclass¶
UserRepository:
connection: [] # extern: from SQLiteDatabase
userCountSql: [] # extern: provided by app
User: # scope-as-dataclass
userId: []
name: []
_count:
- [FFI, SqliteScalarQuery]
- connection: [connection]
sql: [userCountSql]
userCount: [_count, scalar]
User:is a nested scope with two extern fields — the.mixin.yamlequivalent of@scope class Userwith@public @externfields. It acts as a dataclass constructor:current_user(below) will supply values foruser_idandname.connection: []declares thatUserRepositoryexpects aconnectionfrom outside. When composed withSQLiteDatabaseinsideapp, this extern is satisfied bySQLiteDatabase.connection— resolved by name through lexical scoping.
UserRepository.Request — ANF style, cross-scope references¶
Request:
userId: [] # extern: from HttpHandlers
userQuerySql: [] # extern: from app.Request
_params:
- [FFI, TupleWrap]
- element: [userId]
_row:
- [FFI, SqliteRowQuery]
- connection: [connection] # lexical: finds UserRepository.connection
sql: [userQuerySql]
parameters: [_params, wrapped]
_identifier:
- [FFI, GetItem]
- sequence: [_row, row]
index: 0
_name:
- [FFI, GetItem]
- sequence: [_row, row]
index: 1
currentUser:
userId: [_identifier, element]
name: [_name, element]
currentUserName: [currentUser, name]
This section demonstrates A-Normal Form (ANF): every intermediate result
must be bound to a named field. You cannot write
GetItem(sequence=SqliteRowQuery(...).row, index=0) — instead, _row holds
the query result, and _identifier extracts column 0 from _row.row. The
cost is verbosity; the benefit is that every intermediate value is inspectable
and independently composable.
Cross-scope lexical reference: connection: [connection] inside
Request finds UserRepository.connection — the lexical scope chain
searches outward through parent, grandparent, etc. No import statement is
needed; the scope hierarchy is the namespace.
Constructing ``current_user``: Instead of calling User(user_id=..., name=...),
the .mixin.yaml directly defines current_user as a scope with two fields. The
User scope-as-dataclass above establishes the field names; here those same
names are filled with concrete values.
HttpHandlers — flat inheritance, qualified this¶
HttpHandlers:
userCount: [] # extern: from UserRepository
Request:
- [FFI, ExtractUserId] # provides: userId
- [FFI, HttpSendResponse] # provides: written
- request: [] # extern: injected per-request
pathSeparator: [] # extern: from app.Request
responseTemplate: [] # extern: from app.Request
statusCode: 200 # inline scalar
currentUserName: [] # extern: from UserRepository.Request
_format:
- [FFI, FormatResponse]
- responseTemplate: [responseTemplate]
userCount: [userCount]
currentUserName: [currentUserName]
responseBody: [_format, responseBody]
body: [responseBody]
# Qualified this: fails loudly if HttpSendResponse is not composed,
# unlike `written: []` which silently creates an empty scope.
response: [Request, ~, written]
Flat inheritance: Request inherits two FFI adapters in its
inheritance list (- [FFI, ExtractUserId], - [FFI, HttpSendResponse]). Their
@extern and @resource fields all merge into Request’s own field
namespace. The last list item (the mapping starting with request: []) defines
Request’s own fields.
Lexical scoping across scope boundaries: [user_count] inside
Request searches outward and finds HttpHandlers.user_count. At this
point user_count is just an extern [] — its actual value comes from
UserRepository after deep merge (explained below).
Qualified this: [Request, ~, written] — instead of declaring
written: [] and writing response: [written], this navigates the runtime
composition graph to access the written property inherited from
HttpSendResponse. The advantage: if HttpSendResponse is accidentally not
composed, this fails with an error instead of silently creating an empty scope.
NetworkServer — deep merge, config scoping¶
NetworkServer:
- [FFI, HttpServerCreate]
- host: []
port: []
Request: []
_handler:
- [FFI, HttpHandlerClass]
- Request: [Request]
handlerClass: [_handler, handlerClass]
All the scopes above live in Library.mixin.yaml. They reference [FFI, Xxx] which
resolves to abstract declarations at the top of the file — no concrete Python
code is involved yet.
Apps.mixin.yaml — integration entry point¶
Apps.mixin.yaml is a separate file that inherits the real FFI implementation and
the Library, then defines concrete application entries:
# Apps.mixin.yaml — integration entry points: Library + real FFI + configuration values.
# Named "Apps" (plural) because multiple entry points can coexist here.
- [StdlibFFI] # inherit real Python FFI adapters
- [Library] # inherit business logic
- memoryApp:
- [Apps, ~, SQLiteDatabase] # qualified this: inherited from Library
- [Apps, ~, UserRepository]
- [Apps, ~, HttpHandlers]
- [Apps, ~, NetworkServer]
- databasePath: ":memory:"
setupSql: |
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users VALUES (1, 'alice');
INSERT INTO users VALUES (2, 'bob');
userCountSql: "SELECT COUNT(*) FROM users"
host: "127.0.0.1"
port: 0
Request:
userQuerySql: "SELECT id, name FROM users WHERE id = ?"
pathSeparator: "/"
responseTemplate: "total={total} current={current}"
Library/FFI separation: - [stdlib_ffi] makes the real FFI module
(Python @scope classes) visible. - [Library] makes the business logic
visible. When composed, stdlib_ffi.FFI (real implementations) deep-merges
with Library.FFI (abstract declarations), and the real @resource methods
override the [] slots. The business logic never imports Python directly.
Portability: To run the same business logic on a different runtime, replace
- [stdlib_ffi] with a different FFI package — the Library.mixin.yaml file needs
no changes. To test with mocks, provide a mock FFI module instead of
stdlib_ffi.
“What Color Is Your Function?” (blog post):
The same Library.mixin.yaml runs unchanged on both synchronous and asynchronous
runtimes. Replacing - [stdlib_ffi] with - [async_ffi] swaps the FFI layer
to one built on aiosqlite + starlette
(implementation) — the business
logic never knows whether it is sync or async. Function color is confined
entirely to the FFI boundary; Library.mixin.yaml itself is colorless.
Composition via inheritance: memory_app inherits four scopes via
qualified this ([Apps, ~, SQLiteDatabase] etc.) because these scopes are
inherited properties, not own properties of Apps.mixin.yaml. This is not four
separate instances — it is a single scope with all four merged together. The
last list item supplies concrete values for every [] extern.
Deep merge: Both UserRepository and HttpHandlers define a
Request. When composed inside memory_app, these merge by name into a
single Request. After merging:
user_id(fromHttpHandlers.RequestviaExtractUserId) becomes visible toUserRepository.Request, which uses it to look upcurrent_usercurrent_user_name(fromUserRepository.Request) becomes visible toHttpHandlers.Request, which uses it in_format
Neither scope imports or references the other — deep merge makes their fields mutual siblings automatically. This is the most powerful feature of MIXINv2: cross-cutting concerns compose without glue code.
Config value scoping: App-lifetime values (database_path, host, port)
live directly in memory_app. Request-lifetime values (user_query_sql,
path_separator, response_template) live in memory_app.Request — they
are only needed during request handling.
Syntax quick reference¶
Syntax |
Meaning |
|---|---|
|
Extern — value must be provided by a parent scope or caller |
|
Lexical reference — look up |
|
Path navigation — access |
|
Qualified this — access inherited |
|
Scalar value — string, number, etc. |
|
Private — not visible to external callers (leading underscore) |
|
Nested scope — a child scope with its own fields |
|
Inheritance — |
Python vs MIXINv2¶
Aspect |
Python |
MIXINv2 ( |
|---|---|---|
Composition |
Manual |
Inheritance list: |
Dependency injection |
|
|
Expression style |
Nested function calls |
ANF: every intermediate has a name |
Cross-cutting concerns |
Explicit adapter / glue code |
Deep merge: same-named scopes auto-merge |
Accessing inherited members |
|
Qualified this: |
Business logic location |
Mixed with I/O in Python |
Separate |
Configuration |
Kwargs at call site |
Scalar values in |
Evaluation¶
import tests.fixtures.app_mixin as app_mixin
from mixinv2 import evaluate
# evaluate() auto-discovers stdlib_ffi/, Library.mixin.yaml, and Apps.mixin.yaml.
root = evaluate(app_mixin, modules_public=True)
# Access the composed app — Apps is the .mixin.yaml file name.
composed_app = root.Apps.memory_app
composed_app.server # HTTPServer on 127.0.0.1:<assigned port>
composed_app.connection # sqlite3.Connection to :memory:
composed_app.user_count # 2
# Create a fresh request scope (per-request resources):
scope = composed_app.Request(request=fake_request)
scope.current_user.name # "alice"
scope.response # sends HTTP response as side effect
Swapping configuration is just a different entry in Apps.mixin.yaml — the Python
FFI adapters and Library.mixin.yaml never change. Swapping the FFI layer is just a
different @scope module — the .mixin.yaml business logic never changes.
Runnable tests for this example are in packages/mixinv2-examples/tests/test_readme_package_examples.py, using the fixture package at packages/mixinv2-examples/src/mixinv2_examples/app_mixin/.
The full language specification is in MIXINv2 Specification.
The semantics of MIXINv2 are grounded in the inheritance-calculus, a formal calculus of deep-mergeable mixins.