I'm trying to understand how ORMs are able to resolve table columns via a class attribute without explicitly providing table and column names.
from orm import Column
class Car(Model):
id = Column()
Queries then are able to do this:
Car.select().where(Car.id == 7)
I understand that Column defines a method for the __eq__ operator, but how would a column instance know it is referring to class Car and attribute id (assuming those are used for table and column names by default)?
I'm learning a lot about python by digging into how ORM libs work!
In SqlAlchemy and DJango ORM, the base class (Model here) is tie to a metaclass or a factory method (like __new__) which performs introspection.
Here is how you can reproduce with a function (simpler solution):
class Model():
pass
class Column():
def __init__(self, name=None):
self.name = name
def setup_class(cls):
for name, value in cls.__dict__.items():
if isinstance(value, Column):
attr = getattr(cls, name)
attr.name = attr.name or name
This setup_class function introspect the cls class and setup the name attribute if it is empty or None, for instance:
class Car(Model):
id = Column()
from pprint import pprint
pprint(Car.id.name)
# -> None
setup_class(Car)
pprint(Car.id.name)
# -> 'id'
Edit
implementation example using metaclass:
class Column():
def __init__(self, name=None):
self.name = name
class MyMeta(type):
def __new__(cls, name, bases, attrs):
for name, col in attrs.items():
if isinstance(col, Column):
col.name = col.name or name
return super(MyMeta, cls).__new__(cls, name, bases, attrs)
class Model(metaclass=MyMeta):
pass
class Car(Model):
id = Column()
import pprint
pprint.pprint(Car.id.name)
# -> 'id'
Of course, that's not enough. You have to understand that Column is a descriptor. This descriptor is used to build the SQL queries.
Related
I have 2 classes:
class Parent(object):
def __init__(self, id, name):
self.id = id
self.name = name
self.__parent_vars = ['id', 'name'] # make a copy
def print_values(self):
res = {}
for el in self.__parent_vars:
res[el] = vars(self)[el]
return res
class Child(Parent):
def __init__(self, id, name, last_name, age):
Parent.__init__(self, id, name)
self.last_name = last_name
self.age = age
What I want to do - is to get from Child parameters of Parent class. I made it using additional variable and it works, but I need more elegant solution without additional variable. I need it for pickle class. If I create additional variable it breaks my Schemas in a big project.
I try to find something like this:
c = Child(12,"Foo","whatever",34)
vars(c.super())
with expected output:
{'id': 12, 'name': 'Foo'}
I found this question: Get attributibutes in only base class (Python) but it has significant difference of mine, so I can't use that solution.
I am afraid you cannot easily. In Python, classes only carry methods and static attributes. Non
static attributes are commonly stored in the __dict__ attribute of the objects. That means that except in special cases, you cannot easily know which attributes where assigned in a parent class method, in a child class method or even outside any method.
I can only imagine a meta_class that would instrument the __init__ method to store which attributes were changed during its call:
import collections
import functools
import inspect
class Meta_attr(type):
init_func = {}
attrs = collections.defaultdict(set)
def __new__(cls, name, bases, namespace, **kwds):
c = type.__new__(cls, name, bases, namespace, **kwds)
cls.init_func[c] = c.__init__
#functools.wraps(c.__init__)
def init(self, *args, **kwargs):
before = set(self.__dict__.keys())
cls.init_func[c](self, *args, **kwargs)
after = set(self.__dict__.keys())
cls.attrs[c].update(after.difference(before))
init.__signature__ = inspect.signature(c.__init__)
c.__init__ = init
return c
class Parent(object, metaclass=Meta_attr):
def __init__(self, id, name):
self.id = id
self.name = name
def print_values(self):
res = {}
for el in Meta_attr.attrs[Parent]:
res[el] = vars(self)[el]
return res
class Child(Parent):
def __init__(self, id, name, last_name, age):
Parent.__init__(self, id, name)
self.last_name = last_name
self.age = age
It gives:
>>> c = Child(1,"a","b", 20)
>>> c.print_values()
{'id': 1, 'name': 'a'}
BEWARE: if an attribute is set outside of the __init__ method, it will not be registered by this meta class...
I found one more solution. It looks simplier, so I used it. It based on using Marshmallow Schemas.
Also It is more usiful in my project because I already used marshmallow Schemas.
The idea: I create 2 classes and 2 different Schemas. And can serialize bigger class (Child) using just Parent schema:
Code
Define 2 classes:
class Parent(object):
def __init__(self):
self.id = 'ID'
self.name = 'some_name'
class Child(Parent):
def __init__(self):
Parent.__init__(self)
self.last_name = 'last_name'
self.age = 15
from marshmallow import Schema, fields, EXCLUDE
Define 2 Schemas:
class ParentSchema(Schema):
ba = Parent()
id = fields.Str(missing=ba.id)
name = fields.Str(missing=ba.name)
class ChildSchema(Schema):
ba = Child()
id = fields.Str(missing=ba.id)
name = fields.Str(missing=ba.name)
last_name = fields.Str(missing=ba.last_name)
age = fields.Int(missing=ba.age)
use it to get only Parent attributes for Child object:
user_data = {'id':'IDIDID', 'name':'add_name', 'last_name':'FAMILIYA'}
res = ParentSchema().load(user_data, unknown=EXCLUDE)
# output:
res
{'name': 'add_name', 'id': 'IDIDID'}
I'm trying to build some "ORM like" behavior in Python. To do this I have a Model class and would like to produce any kind of subclasses. My problem is when I try to list the attributes of my subclasses instances.
Here is my Model class:
class Model:
def __init__(self):
self.className = type(self).__name__
def listAttributes(self):
from modules.users.user import User
instance = User()
meta = vars(instance)
And a User subclass :
class User(models.Model):
name = models.FieldChar()
password = models.FieldChar()
age = models.FieldInteger()
When I invoke the listAttributes method from a User instance, I don't have what I expect, but probably what I should: the meta var contains only className. Here's how I invoke the method:
from modules.users import user
user = user.User()
user.listAttributes()
Same behavior when I override the listAttributes method in User():
def listAttributes(self):
meta = vars(self)
print(meta)
Is there a way to list the attribute of the object from which I call the listAttributes method?
Thanks for your lights!
You could make your listAttributes method a classmethod and try the following:
class Model(object):
#classmethod
def listAttributes(cls):
return [
a for a in dir(cls) if not
callable(getattr(cls, a)) and not
a.startswith("__")
]
If you just want to have the attributes that are of your "Fields" type (FieldChar, FieldInt...) then you could make a base class for the fields called e.g. ModelField and also check your attributes against this base type:
class Model(object):
#classmethod
def listAttributes(cls):
attributes = []
for a in dir(cls):
if ((not callable(getattr(cls, a)) and not
a.startswith("__") and
getattr(cls, a).__class__.__bases__[0] == ModelField)):
attributes.append(a)
You could even return the objects instead of just the names. Or append the objects to a dict in your Model class (to make usage in your code easier). I constructed my ORM like this:
class ModelField(object):
...
class IntField(ModelField):
...
class Model(object):
id = IntField(pk=True)
def __init__(self, *args, **kwargs):
try:
self.modelFields
except AttributeError:
self.__class__.introspect()
for key, modelfield in self.modelFields.items():
setattr(self, key, kwargs.get(key, None))
#classmethod
def introspect(cls):
cls.modelFields = {}
for a in dir(cls):
if ((not callable(getattr(cls, a)) and not
a.startswith("__") and
getattr(cls, a).__class__.__bases__[0] == ModelField)):
cls.modelFields[a] = getattr(cls, a)
Then i used cls.introspect to set up the modelFields dict and use this dict to e.g. autogenerate a db-table or construct querys by using attributes of the ModelField class.
I know there is a way to instrument SQLAlchemy to prepend common prefix to all columns, is it possible to add a common prefix to all table names derived from single declarative_base?
Use declared_attr and a customized Base along the lines of:
from sqlalchemy.ext.declarative import declared_attr
class PrefixerBase(Base):
__abstract__ = True
_the_prefix = 'someprefix_'
#declared_attr
def __tablename__(cls):
return cls._the_prefix + cls.__incomplete_tablename__
class SomeModel(PrefixerBase):
__incomplete_tablename__ = 'sometable'
...
A class marked with __abstract__ will not get a table or mapper created and can act as the extended declarative base.
You could also make it even more implicit using a custom metaclass (originally described here):
from sqlalchemy.ext.declarative.api import DeclarativeMeta
class PrefixerMeta(DeclarativeMeta):
def __init__(cls, name, bases, dict_):
if '__tablename__' in dict_:
cls.__tablename__ = dict_['__tablename__'] = \
'someprefix_' + dict_['__tablename__']
return super().__init__(name, bases, dict_)
Base = declarative_base(metaclass=PrefixerMeta)
class SomeModel(Base):
__tablename__ = 'sometable'
...
So there is a class called Field and it has class attribute _slots, this attribute is dictionary.
So it looks like this (that class also use custom __metaclass__):
class Field(object):
_slots = {
'key1': False,
'key2': None,
...
}
Class content is too big to be pasted here, so I'm providing a link to it, src
Now I need that when you instantiate this class, it will have my custom key/value pair inside that attribute.
So I tried this (in my own module):
from openerp import fields
fields.Field._slots['custom_key'] = None
When I print after this assignment, then it shows me that I have set this key/value pair, but actually when that class is loaded, it never gets my custom_key. Though if I monkey patch some method, it works, but for class attribute it does not. Is it even possible to do that (or am I doing something wrong here?)?
Though if I write simple test scenario with my own simple class, like:
(f.py file)
class F(object):
_t = {'a': 1}
(other python file)
import f
f.F._t['b'] = 10
f_new = f.F()
print f_new._t
Then this one works.
Update
I also tried this (modifying code directly) in MetaField class that is used as __metaclass for Field (still that is ignored when Field class uses _slots:
def __new__(meta, name, bases, attrs):
""" Combine the ``_slots`` dict from parent classes, and determine
``__slots__`` for them on the new class.
"""
base_slots = {}
for base in reversed(bases):
base_slots.update(getattr(base, '_slots', ()))
slots = dict(base_slots)
slots.update(attrs.get('_slots', ()))
attrs['__slots__'] = set(slots) - set(base_slots)
attrs['_slots'] = slots
attrs['_slots']['custom_key'] = None # <= Modification here
return type.__new__(meta, name, bases, attrs)
When using base class to create sub class attribute, I got an error that sub class has no member which I created in the base class method. Codes are shown below
class Base(object):
def setAttributes(self, data):
for key in data:
self.key = data[key]
class Sub(Base):
pass
data = {'id':1, 'name': 'Name'}
a = Sub()
Base.setAttributes(a, data)
print(a.id)
And the error messages are : AttributeError: 'Sub' object has no attribute 'id'.
Is there any solution to use base class to assign sub class attributes in python?
This has nothing to do with inheritance. You have assigned the value to a.key not a.id. In fact you have assigned all your values to a.key.
I believe you want setattr(self, key, data[key]) in place of self.key = data[key]. Or just do away with the loop entirely and do self.__dict__.update(data).