Designing a Custom ORM Using Python Metaclasses

Class ↔ Table, an object instance ↔ a row, an object attribute ↔ a column value, object.attribute ↔ field

  • Store an object → dict → JSON → MySQL
  • MySQL → JSON → dict → reconstruct object
  1. Convert all attribute names and values of an object into keys and values of a dictionary
  2. Serialize the mapping dictionary into JSON format
  3. Encode the JSON data and transmit it to the server
  4. On the server, decode the binary data and deserialize it to store into a MySQL data base

Custom Metaclass Implementation

'''
Custom metaclass:
    Inherits from type, overrides __new__ to automatically manipulate
    the class namespace when a class is created.
    Responsibilities include:
        1. Ensuring every table class has a table_name attribute (except for the base model class)
        2. Verifying that exactly one field is designated as the primary key
        3. Separating field objects into a dedicated dictionary for easy access,
           because the field attribute name matches the internal field name
'''

class ModelMeta(type):
    def __new__(mcs, class_name, bases, namespace):

        # Skip processing for the base 'AbstractModel' class
        if class_name == 'AbstractModel':
            return type.__new__(mcs, class_name, bases, namespace)

        # Assign a default table name if none is provided
        table = namespace.get('table_name')
        if not table:
            namespace['table_name'] = class_name.lower()

        # Container for all field instances found in the class
        fields_map = {}

        # Track primary key occurrences
        pk_counter = 0
        pk_field_name = None

        for attr, val in namespace.items():
            if isinstance(val, Field):          # Keep only actual field definitions
                fields_map[attr] = val
                if val.is_primary:
                    pk_field_name = attr
                    pk_counter += 1

        if pk_counter > 1:
            raise TypeError('A table must have exactly one primary key.')
        if pk_counter == 0:
            # Default to the first field if none is marked as primary
            first_key = list(fields_map)[0]
            pk_field_name = fields_map[first_key].column_name

        # Remove field objects from the class namespace to save memory
        for key in fields_map:
            del namespace[key]

        # Store the primary key attribute name and the fields mapping
        namespace['pk_name'] = pk_field_name
        namespace['field_defs'] = fields_map

        return type.__new__(mcs, class_name, bases, namespace)

Defniing the Abstract Model Class

# Base model class using dict as a data container
class AbstractModel(dict, metaclass=ModelMeta):
    # Allow attribute-style assignment to update dictionary entries
    def __setattr__(self, key, val):
        self[key] = val

    # Allow attribute-style retrieval from dictionary entries
    def __getattr__(self, item):
        return self.get(item)


# Custom field types
class Field:
    def __init__(self, column_name, is_primary=False):
        self.column_name = column_name
        self.is_primary = is_primary

class IntField(Field):
    pass

class StrField(Field):
    pass


# Define a User table model
class UserModel(AbstractModel):
    table_name = 'user_info'

    id = IntField('id', is_primary=True)
    username = StrField('username')
    password = StrField('password')


# Define a Movie table model
class MovieModel(AbstractModel):
    id = IntField('id', is_primary=True)
    title = StrField('title')
    genre = StrField('genre')


# Creating instances
user1 = UserModel(id=1, username='alice', password='secret')
print(UserModel.__dict__)

Tags: python metaclasses ORM Object-Relational Mapping Database Abstraction

Posted on Thu, 20 Aug 2026 16:33:37 +0000 by Shellfishman