You actually asked two questions here.
First, you asked "What sort of trickery is SQLAlchemy doing". There is a bit more going on behind the scenes using a concept called Metaclasses to dynamically create the Base
class.
But in reality all you need to know is that SQLAlchemy is defining a constructor (albeit in a roundabout way) in the Base
class that dynamically sets the elements. Here is actually the implementation of that method (at least as it exists at the time of this answer):
def _declarative_constructor(self, **kwargs):
"""A simple constructor that allows initialization from kwargs.
Sets attributes on the constructed instance using the names and
values in ``kwargs``.
Only keys that are present as
attributes of the instance's class are allowed. These could be,
for example, any mapped columns or relationships.
"""
cls_ = type(self)
for k in kwargs:
if not hasattr(cls_, k):
raise TypeError(
"%r is an invalid keyword argument for %s" %
(k, cls_.__name__))
setattr(self, k, kwargs[k])
Basically, this is dynamically determining the keyword arguments, and setting the attributes on the new object automatically. You can imagine theBase
class as looking like the following, although keep in mind it is actually a bit more complex (you can read the code to find out more):
class Base(object):
def __init__(self, **kwargs):
cls_ = type(self)
for k in kwargs:
if not hasattr(cls_, k):
raise TypeError(
"%r is an invalid keyword argument for %s" %
(k, cls_.__name__))
setattr(self, k, kwargs[k])
If you created the above code, any class that you create that inherits from Base
would automatically get the ability to have the attributes of the property auto-filled as long as the attribute was already defined on the class. This is because of the typical Python Object-Oriented inheritance structure: if you don't define a method on your User
object, Python looks for the method being defined on a base class (in this case the Base
method) and will use that instead. This goes for the __init__
method, just like any other method.
Your second question is "Why don't they just use a constructor (i.e an __init__
method)?" Well, as we've described above, they do! They set the _declarative_constructor
method to the __init__
attribute the Base
class, effectively setting the default logic for object construction. Of course, this only defines the default; you can always override this if you want to...
class User(Base):
__tablename__ = 'users'
id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
name = Column(String(50))
fullname = Column(String(50))
password = Column(String(12))
def __init__(self, name):
self.name = name
self.fullname = name
self.password = generate_new_password()
# The following will now work...
ed_user = User('ed')
mark_user = User(name='mark')
# ...but this will not...
problem_user = User(name='Error', fullname='Error M. McErrorson', password='w00t')