Skip to content

Model Meta & Introspection

Access model metadata and schema information programmatically.


Overview

Aksara models expose metadata through the _meta attribute, enabling introspection for:

  • Building admin interfaces
  • Generating API schemas
  • Creating migrations
  • AI-powered tooling
from myapp.models import Post

# Access meta information
print(Post._meta.model_name)     # "Post"
print(Post._meta.table_name)     # "posts"
print(Post._meta.fields)         # List of field objects

Meta Class Options

Configure model behavior using the inner Meta class:

from aksara import Model, fields

class Post(Model):
    title = fields.String(max_length=200)
    content = fields.Text()
    created_at = fields.DateTime(auto_now_add=True)

    class Meta:
        table_name = "blog_posts"           # Custom table name
        ordering = ["-created_at"]          # Default ordering
        unique_together = [("author", "slug")]  # Composite unique
        indexes = [                         # Database indexes
            ("created_at",),
            ("author_id", "created_at"),
        ]
        verbose_name = "Blog Post"          # Human-readable name
        verbose_name_plural = "Blog Posts"  # Plural name

Available Options

Option Type Default Description
table_name str {model_name}s Database table name
ordering list[str] [] Default ordering
unique_together list[tuple] [] Composite unique constraints
indexes list[tuple] [] Database indexes
verbose_name str {model_name} Human-readable name
verbose_name_plural str {verbose_name}s Plural name
abstract bool False Abstract base model

Accessing _meta

Model Information

# Model name
Post._meta.model_name        # "Post"
Post._meta.verbose_name      # "Blog Post" or "post"
Post._meta.verbose_name_plural  # "Blog Posts" or "posts"

# Database table
Post._meta.table_name        # "blog_posts" or "posts"
Post._meta.db_table          # Alias for table_name

# App information
Post._meta.app_label         # "myapp"

Field Information

# All fields
fields = Post._meta.fields  # List[FieldInfo]

# Get field by name
title_field = Post._meta.get_field("title")
print(title_field.name)           # "title"
print(title_field.field_type)     # "String"
print(title_field.max_length)     # 200

# Field names
names = Post._meta.field_names    # ["id", "title", "content", ...]

# Concrete fields (excluding relations)
concrete = Post._meta.concrete_fields

# Primary key
pk_field = Post._meta.pk
print(pk_field.name)              # "id"

Relation Information

# All relations
relations = Post._meta.relations  # List[RelationInfo]

# ForeignKey fields
fks = Post._meta.foreign_keys     # List[ForeignKeyInfo]

# ManyToMany fields
m2ms = Post._meta.many_to_many    # List[ManyToManyInfo]

# Reverse relations
reverse = Post._meta.reverse_relations

Field Introspection

FieldInfo Object

field = Post._meta.get_field("title")

# Basic info
field.name                  # "title"
field.field_type           # "String"
field.python_type          # str
field.db_column            # "title" (database column name)

# Constraints
field.max_length           # 200
field.nullable             # False
field.unique               # False
field.primary_key          # False
field.default              # None

# Choices (for Enum fields)
field.choices              # None or list of choices

# AI metadata (if defined)
field.ai_description       # "The title of the blog post"
field.ai_visible           # True

Checking Field Types

from aksara.fields import String, ForeignKey, ManyToMany

field = Post._meta.get_field("author")

# Check type
isinstance(field, ForeignKey)  # True

# Or use string check
field.field_type == "ForeignKey"  # True

# Check if relation
field.is_relation              # True
field.is_foreign_key           # True
field.is_many_to_many          # False

Relation Introspection

ForeignKey Meta

author_field = Post._meta.get_field("author")

# Related model
author_field.related_model        # User class
author_field.related_model_name   # "User"

# Relation details
author_field.on_delete            # "CASCADE"
author_field.related_name         # "posts"
author_field.db_column            # "author_id"

ManyToMany Meta

tags_field = Post._meta.get_field("tags")

# Related model
tags_field.related_model          # Tag class

# Junction table
tags_field.through_model          # PostTag (auto or custom)
tags_field.through_table          # "post_tags"

# Relation details
tags_field.related_name           # "posts"

Reverse Relations

# Get reverse relations to a model
for rel in User._meta.reverse_relations:
    print(f"{rel.related_model_name}.{rel.field_name}")
    # "Post.author"
    # "Comment.user"

Practical Use Cases

Generate API Schema

def model_to_schema(model_class):
    """Convert model to OpenAPI schema."""
    properties = {}
    required = []

    for field in model_class._meta.fields:
        if field.name == "id":
            continue

        prop = {
            "type": python_type_to_json(field.python_type),
        }

        if field.max_length:
            prop["maxLength"] = field.max_length

        if field.ai_description:
            prop["description"] = field.ai_description

        properties[field.name] = prop

        if not field.nullable and field.default is None:
            required.append(field.name)

    return {
        "type": "object",
        "properties": properties,
        "required": required,
    }

Build Admin Interface

def get_admin_columns(model_class):
    """Get columns for admin list view."""
    columns = []

    for field in model_class._meta.fields:
        columns.append({
            "name": field.name,
            "label": field.name.replace("_", " ").title(),
            "sortable": not field.is_relation,
            "type": field.field_type,
        })

    return columns

Dynamic Form Generation

def model_to_form_fields(model_class):
    """Generate form fields from model."""
    form_fields = {}

    for field in model_class._meta.concrete_fields:
        if field.name == "id" or field.name.endswith("_at"):
            continue

        form_field = {
            "name": field.name,
            "required": not field.nullable,
            "type": get_input_type(field),
        }

        if field.max_length:
            form_field["maxlength"] = field.max_length

        if field.choices:
            form_field["options"] = field.choices

        form_fields[field.name] = form_field

    return form_fields

AI Metadata

Aksara models support AI-specific metadata for LLM integration.

Field-Level AI Metadata

class Product(Model):
    name = fields.String(
        max_length=200,
        ai_description="The product name shown to customers",
    )
    price = fields.Decimal(
        max_digits=10,
        decimal_places=2,
        ai_description="Price in USD, must be positive",
    )
    sku = fields.String(
        max_length=50,
        ai_visible=False,  # Hide from AI tools
    )

Model-Level AI Metadata

class Product(Model):
    class Meta:
        ai_description = "Products available for sale"
        ai_examples = [
            "List all products under $50",
            "Find products with 'laptop' in the name",
        ]

Accessing AI Metadata

# Field AI description
field = Product._meta.get_field("price")
print(field.ai_description)  # "Price in USD, must be positive"

# Model AI metadata
print(Product._meta.ai_description)  # "Products available for sale"
print(Product._meta.ai_examples)     # List of example queries

# Get all AI-visible fields
visible_fields = [
    f for f in Product._meta.fields
    if f.ai_visible
]

Constraints Introspection

# Unique together constraints
for constraint in Post._meta.unique_together:
    print(constraint)  # ("author", "slug")

# Indexes
for index in Post._meta.indexes:
    print(index)  # ("created_at",) or ("author_id", "created_at")

# Check constraints (from field definitions)
for field in Post._meta.fields:
    if field.unique:
        print(f"{field.name} has unique constraint")

Complete Example

from aksara import Model, fields

class Article(Model):
    """Blog article model with full metadata."""

    title = fields.String(
        max_length=200,
        ai_description="Article headline",
    )
    slug = fields.String(
        max_length=200,
        unique=True,
        ai_description="URL-safe identifier",
    )
    content = fields.Text(
        ai_description="Full article content in Markdown",
    )
    author = fields.ForeignKey(
        "User",
        on_delete="CASCADE",
        related_name="articles",
    )
    category = fields.ForeignKey(
        "Category",
        on_delete="SET_NULL",
        nullable=True,
        related_name="articles",
    )
    tags = fields.ManyToMany(
        "Tag",
        related_name="articles",
    )
    view_count = fields.Integer(
        default=0,
        ai_visible=False,  # Internal metric
    )
    is_published = fields.Boolean(default=False)
    published_at = fields.DateTime(nullable=True)
    created_at = fields.DateTime(auto_now_add=True)
    updated_at = fields.DateTime(auto_now=True)

    class Meta:
        table_name = "articles"
        ordering = ["-published_at"]
        unique_together = [("author", "slug")]
        indexes = [
            ("is_published", "published_at"),
            ("category_id",),
        ]
        verbose_name = "Article"
        verbose_name_plural = "Articles"
        ai_description = "Blog articles with rich content"
        ai_examples = [
            "Find published articles by author",
            "Get articles in the Python category",
        ]


# Introspection example
def describe_model(model_class):
    """Generate a complete model description."""
    meta = model_class._meta

    description = {
        "name": meta.model_name,
        "table": meta.table_name,
        "description": getattr(meta, "ai_description", None),
        "fields": [],
        "relations": [],
        "constraints": {
            "unique_together": list(meta.unique_together),
            "indexes": list(meta.indexes),
        },
    }

    for field in meta.fields:
        field_info = {
            "name": field.name,
            "type": field.field_type,
            "required": not field.nullable and field.default is None,
            "unique": field.unique,
        }

        if field.is_relation:
            field_info["related_to"] = field.related_model_name
            description["relations"].append(field_info)
        else:
            description["fields"].append(field_info)

    return description


# Usage
info = describe_model(Article)
print(info)
# {
#     "name": "Article",
#     "table": "articles",
#     "description": "Blog articles with rich content",
#     "fields": [
#         {"name": "title", "type": "String", "required": True, "unique": False},
#         ...
#     ],
#     "relations": [
#         {"name": "author", "type": "ForeignKey", "related_to": "User"},
#         ...
#     ],
#     ...
# }