Aylwyn Lake
Aylwyn Lake

Reputation: 2027

SQLAlchemy autoload insert record

PostgreSQL has table student:

                                Table "public.student"
 Column |         Type          |                      Modifiers                   
--------+-----------------------+------------------------------------------------------
 id     | integer               | not null default nextval('student_id_seq'::regclass)
 name   | character varying(10) |
 sex    | character varying(6)  |
 age    | integer               |
Indexes:
    "student_pkey" PRIMARY KEY, btree (id)

I can insert a record like this:

Base = declarative_base()

class TableObject(Base):
    __table__ = Table('student', metadata, autoload=True)   

record = TableObject(name="tom", sex="male")
session.add(record)
record = TableObject(name="alice", sex="female", age=10)
session.add(record)

I want there is a method to be like this:

record = TableObject("alice", "female", 10)
session.add(record)

cause error

TypeError: __init__() takes exactly 1 argument (4 given)

Because I will get records from a file, and split every line to lists, so if this method was supported, it would be very convenient.

Is there any way?

Upvotes: 3

Views: 635

Answers (2)

Evgenii
Evgenii

Reputation: 3420

Can you use method like this:

class TableObject(Base):
    def __init__(self, name, sex, age):
        super(TableObject, self).__init__()
        self.name = name
        self.sex = sex
        self.age = age

?

Upvotes: 0

van
van

Reputation: 76962

You can insert the records right now because declarative_base includes a constructor which takes named parameters. The code of that construtor is below (verbatim from github repository):

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])
_declarative_constructor.__name__ = '__init__'

So if you would like to replace this constructor with the positional version and therefore it will apply to all your models. The below could be a place to start:

def _declarative_positional_constructor(self, *args, **kwargs):
    assert len(kwargs) == 0
    column_names = tuple(c.name for c in self.__mapper__.columns if not(c in self.__mapper__.primary_key))
    assert len(column_names) == len(args)
    for name, value in zip(column_names, args):
        setattr(self, name, value)
_declarative_positional_constructor.__name__ = '__init__'

# ...
# use own default constructor
Base = declarative_base(constructor = _declarative_positional_constructor)

In this case, however, the way to create instances using names will not work. So you might enhance this to handle both versions.
Another thing to check is weather the autoload guaranties the order of the columns in the table(s) is the same in order to prevent "male" being stored as a name while "John" gets written to Gender column.

Upvotes: 2

Related Questions