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
- Convert all attribute names and values of an object into keys and values of a dictionary
- Serialize the mapping dictionary into JSON format
- Encode the JSON data and transmit it to the server
- 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__)