I made my own ORM mapper, but I implemented the process of passing the comparison operator as an argument to the where method. It would be nice if you could imagine how to use it as follows. You can specify each condition of the where clause more intuitively.
Session.select('coulmn_name1', 'coulmn_name2').where(Model.id > 1, _and, Model.name == "Mike")
If Model.id> 1,
is passed as an argument, True or False is usually passed, and ** id means greater than 1 ** cannot be passed. However, if you rewrite the special method, you can pass the argument without processing it as a comparison operator.
In this where method, only the process of "returning the where clause as a character string" is implemented. It's simple to do, but knowing this implementation proved useful for metaprogramming.
I think that useful information can be obtained for such people. However, I will not explain metaprogramming itself in this article. If you know metaprogramming and want to see an example, this article will help you.
If you have a table that handles a person called Person, declare the Person model in the class as follows.
from model_base import *
class Person(ModelBase):
id = {'default': 1}
name = {'default': 'Mike'}
All you have to do is declare the required column names in your model. For the initial value, specify the option in the dictionary. This time we only have the defalut option. As long as you inherit ModelBase, the model will be assembled automatically.
When the Person class is loaded, the class fields, ʻid and
name, do not contain dictionaries like
{'default': 1}. The column class object is automatically included by the metaclass. If you want to see
default, just write
pseron_instance.id.default`.
Declare the column name without _ (underscore) at the beginning of the class field. If you use the prefix _ (underscore), it will not be recognized as a column, so please prefix the required methods and fields with _ (underscore) as private.
This time, the where method returns the assembled where clause as a string.
from person import *
if __name__ == '__main__':
db_manager = DBManager()
#Where clause assembly
db_manager.where(Person.id > 1, "or", Person.name == 'Mike')
#Easy to type if prepared with local variables
_and = "and"
_or = "or"
db_manager.where(Person.id > 1, _and, Person.name == 'Mike')
#Check the default
person_A = Person()
print("person_A.id = %s" % person_A.id)
print("person_A.name = %s" % person_A.name)
# Model.Coulm object for id, model.Error because id contains column value
person_A = "Jane"
print(Person.name.default)
# print(person_A.name.default)
# => AttributeError: 'str' object has no attribute 'name'
There are four classes to prepare.
class Column():
def __init__(self, column_name, dict):
#Hold the column name in a class variable
self.column_name = column_name
#Set when receiving the setting value of each column
if dict is not None:
for key, value in dict.items():
if key == "default":
self.default = value
def __setattr__(self, key, value):
#You can check the column type by checking the value and throwing an error
self.__dict__[key] = value
#Overload comparison operator
def __eq__(self, other):
return "%s = %s" % (self.column_name, other)
def __ne__(self, other):
return "%s != %s" % (self.column_name, other)
def __lt__(self, other):
return "%s < %s" % (self.column_name, other)
def __gt__(self, other):
return "%s > %s" % (self.column_name, other)
def __le__(self, other):
return "%s <= %s" % (self.column_name, other)
def __ge__(self, other):
return "%s >= %s" % (self.column_name, other)
class MetaModel(type):
def __new__(cls, cls_name, cls_bases, cls_dict):
for key, value in cls_dict.items():
#Extract only public attributes
if not(key.startswith("_")):
#column(Model class variables)Can retrieve the initial value of
#Hold variable name in Column field
cls_dict[key] = Column(key, value)
return super().__new__(cls, cls_name, cls_bases, cls_dict)
class ModelBase(metaclass=MetaModel):
#Called immediately after instantiating
def __init__(self):
#Set the default value set for the Column object
class_dict = self.__class__.__dict__
for column_key, column_value in class_dict.items():
#Extract only public attributes
if not(column_key.startswith("_")):
setattr(self, column_key, column_value.default)
class DBManager():
def where(self, *args):
statement = "WHERE"
for arg in args:
if arg.upper() in ["AND", "OR"]:
statement += " %s " % arg.upper()
else:
statement += " %s " % arg
print(statement)
I set the metaclass to ModelBase. This is so that you don't have to write metaclass = MetaModel
in the subclass, just write MetaModel
. In addition, the common processing of the model is described here.
The information of each column is managed by the instance by preparing a dedicated class. By doing this, you can implement automatic processing such as validation on each column. If there is no dedicated class, Column class, only column values can be passed.
#Without ModelBase
class MyModel(metaclass=MetaModel):
pass
#If you have ModelBase
class MyModel(ModelBase):
pass
Even if you do not instantiate the Person, ModelMeta, which is the Person's metaclass, will be executed when the Person is loaded. Therefore, a Column object (instance) is dynamically created and set in the id and name of Person's class fields. This Column holds the default value of each in the default of the instance member.
{"default ": 1}
A metaclass called MetaModel can also be defined as a regular class instead of being a metaclass. In that case, you could just use ModelBase. However, in that case, you have to create an instance once. I think you should write the instantiation at the very end of the source code so that it will be executed automatically when the module is loaded. But I don't think it's a good idea.
class Column():
pass
class MetaModel(type):
pass
class DBManager():
pass
class ModelBase():
pass
ModelBase()`
The reason why you need to create an instance once is that __new__
does the process of creating an instance. (Strictly speaking, it's just before. I'll leave the generation to you.) The important thing here is what instance it is. A metaclass is a class for creating a class, that is, an instance of the metaclass. On the other hand, just an instance of a class is an object created from a class you normally use. Usually, an instance is a product of a class, but when we talk about relationships, we can use the expression instance in a metaclass and a class.
The following two relationships are both generated with the blueprint. The right side can be seen as an instance.
With this in mind, if you see that __new__
is what you do when you instantiate, the simple difference between a metaclass and a class __new__
is the order of processing. Organize the last execution order to finish.
** Order of execution **
Metaclass .__ new __
Class .__ new __
Class .__ init__
Recommended Posts