Sqlalchemy - Foreign Value - python

I'm looking for a way to do the following.
class Foo(db.Model):
__tablename__ = 'foos_foo'
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
# Not an entity in the table but
# whenever foo.author_name is called,
# it selects the value from User table
author_name = author.name
The reason why I'm looking for a way to do is this:
class Foo(db.Model, Serializable):
I made a Serializable mixin so that foo.serialize would simply return row values in json.
I wish author.name to be part of this serialization. Of course, there are countless other ways to get author's name and insert it inside the serialized output, but for the benefit of clean code, I wish to find a way to include foreign value in the model.
I use the misnomer 'foreign key' because I have no idea what the most appropriate keyword is.
Thank you in advance.

I ended up with using Marshmallow for object serialization.
http://marshmallow.readthedocs.org/en/latest/index.html
These are the implementations.
model.py
class User(db.Model):
name = db.Column()
class UserSerializer(Serializer):
class Meta:
fields = ('id', 'name')
class FooSerializer(Serializer):
author_name = fields.Nested('UserSerializer')
class Meta:
fields = ('id', 'author_name')
view.py
foos = Foo.query.all()
dict = FooSerializer(foos, many=True).data

Objects are not serialize able.
I think you need implement a method that convert to JSON.
json.dumps works well with dicts. So you can also look at.
http://www.marcstober.com/blog/2007/07/07/serializing-arbitrary-python-objects-to-json-using-dict/
You can implement your own method to_json also.

Related

SQLAlchemy one-to-many relationship - how to properly get the 'many' collection

I'm not sure I properly understand how to get the collection part of the one-to-many relationship.
class ProjectReport(db.Model):
__tablename__ = "project_reports"
id = db.Column(UUID, primary_key=True, default=uuid.uuid4)
project_id = db.Column(UUID, db.ForeignKey("projects.id"), nullable=False)
entries = db.relationship("ProducerEntry", backref="project_report", lazy="dynamic")
class ProducerEntry(Entry):
__tablename__ = "producer_entries"
__mapper_args__ = {"polymorphic_identity": "Entry"}
id = db.Column(UUID, db.ForeignKey("entries.id"), primary_key=True)
project_id = db.Column(UUID, db.ForeignKey("projects.id"), nullable=False)
project_report_id = db.Column(UUID, db.ForeignKey("project_reports.id"), nullable=True)
My problem is that I can't just access the entries field.
for entry in self.entries:
do_something(entry)
This returns NotImplementedError
I managed to get the data via hybrid property but that seems a bit of an overkill since already have the relationship, also it'd get a bit complex for further logic later on.
#hybrid_property
def entries(self):
return ProducerEntry.query.filter_by(project_report_id=self.id)
Ab additional information is that the ProjectReport is basically the common columns of the Entry and Project models, and the project_report_id is nullable, because the entries and projects are generated first and then I can generate the project reports from them. This is how I create the reports:
...
project_report = ProjectReport(date_order=entry.date_order, project_id=entry.project.id)
project_report.entries.append(entry)
...
As far as I know I don't have to add the project_report_id to the producer entry after this.
What am I missing here?
Well yeah, that relationship field returns a query, so I simply should have called:
self.entries.all()
Or anything else which is handling a query.

Get custom attributes of a class Python

Is it possible to get the name of our custom attributes of a class in Python ? For instance, here's my class :
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
login = db.Column(db.String(100))
password = db.Column(db.String(100))
first_name = db.Column(db.String(100))
last_name = db.Column(db.String(100))
email = db.Column(db.String(100))
age = db.Column(db.Integer)
sex = db.Column(db.String(10))
What I want is to get the list of my class attributes (and only those that I defined !). I was thinking about using dir(self) and filtering on those not starting with __ but it's not really revelant because there are other fields who are built-in such as metadata, query and so on.
I saw a function getattr (or getattribute) but it's only for a given field.
I don't want to use a dict of keys because it have to stay generic and I don't want to modify the dict everytime I add a field.
As I'm using SqlAlchemy ORM, I got this when trying self.__dict__ :
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7ffbcf252050>}
I also tried a lot of things such as those described here :
Python dictionary from an object's fields but nothing worked.
Does anyone have a solution ?
Thanks !

How do I extend a SQLAlchemy bound declarative model with extra methods?

For example, I have a declarative class on module a:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
addresses = relationship("Address", backref="user")
Now, in module b I want to use the mapped entity, but add a method:
from a import User
class UserWithExtraMethod(User):
def name_capitalized(self):
return self.name.capitalize()
user = UserWithExtraMethod()
print(user.name_capitalized)
However, when I run the script, I will get the following error:
InvalidRequestError: Multiple classes found for path "User" in the registry of this declarative base. Please use a fully module-qualified path.
What have I missed when declaring the user entity? I would like to reuse the previous declared entity.
I am expecting something would be like:
class UserWithExtraMethod(User):
___magic_reuse_previous_mapper__ = True
def name_capitalized(self):
return self.name.capitalize()
Unless you've got a particular reason to have separate classes, you should just write:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
addresses = relationship("Address", backref="user")
def name_capitalized(self):
return self.name.capitalize()
Since the name_capitalized is not special as far as SQLAlchemy is concerned (it's not a ColumnExpression or some such), it is completely ignored by the mapper.
Actually, there's an even better way to do this; your version works fine for instances of User, but is of no use in sql expressions.
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
class User(Base):
# ... body as before
#hybrid_method
def name_capitalized(self):
return self.name.capitalize()
#name_capitalized.expression
def name_capitalized(cls):
# works for postgresql, other databases spell this differently.
return sqlalchemy.func.initcap(cls.name)
which will allow you to do things like:
>>> print Query(User).filter(User.name_capitalized() == "Alice")
SELECT users.id AS users_id, users.name AS users_name
FROM users
WHERE initcap(users.name) = :initcap_1
Perhaps a little late for this reply. Do you have any other relationships setup that are pointing to User?
For example, if you have Address defined as:
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
address = Column(String(50))
Users = relationship("User", backref="addresses")
when Address is trying to resolve to which User within the declarative base to point to, it will find two of them. To verify try Base._decl_class_registry['User']. This is similar to this topic covered by Michael.
In ./sqlalchemy/ext/declarative/clsregistry.py there is an example on how to use the fully qualified path. In this case it would be changing the relationship within address from Users = relationship("User", backref="addresses") to
Users = relationship("a.User", backref="addresses")
Hope this helps point you in the right direction for debugging.
Hacky, but why not just monkey-patch the User class for your purpose instead of inheriting from it?
# modude b
from a import User
def name_capitalized(self):
return self.name.capitalize()
User.name_capitalized = name_capitalized
user = User() # and it has extra-method as well
print(user.name_capitalized)
This may not work for you. I had a similar issue. I ended up passing an instance of User to UserWithExtraMethod during instantiation
class UserWithExtraMethod(object):
def __init__(self, user):
self.user = user
def name_capitalized(self):
return self.user.name.capitalize()
Hope this helps

sqlalchemy, select using reverse-inclusive (not in) list of child column values

I have a typical Post / Tags (many tags associated with one post) relationship in flask-sqlalchemy, and I want to select posts which aren't tagged with any tag in a list I provide. First, the models I set up:
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
tags = db.relationship('Tag', lazy='dynamic')
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text(50))
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
Something like
db.session.query(Post).filter(Post.tags.name.notin_(['dont','want','these']))
fails with
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Post.tags has an attribute 'name'
which I assume is because tags is a relationship and not a column. I had this working on another project when I was writing the actual SQL manually. This was the SQL that worked:
SELECT * FROM $posts WHERE id NOT IN (SELECT post_id FROM $tags WHERE name IN ('dont','want','these'))
How would I achieve this using the sqlalchemy API?
Pretty straightforward using negated any:
query = session.query(Post).filter(~Post.tags.any(Tag.name.in_(['dont', 'want', 'these'])))
Try this one, easy:
users = session.query(Post).filter(not_(Post.tags.name.in_(['dont', 'want', 'these'])))
Hope this helps!
The notin_ works for me, adjusted example:
db.session.query(Post).filter(Post.tags.notin_(['dont','want','these']))
I thought up a nasty solution, but it works for the time being. I'd be interested to hear if anyone comes up with a smarter method.
ignore_ids = [item.post_id for item in Tag.query.filter(Tag.name.in_(['dont','want','these'])).all()]
Post.query.filter(Post.id.notin_(ignore_ids))

What's the proper way to describe an associative object by SQLalchemy the declarative way

I'm looking for a way to describe an associative object the declarative way. Beyond storing the foreign keys in the association table, I need to store information like the creation date of the association.
Today, my model looks like that :
# Define the User class
class User(Base):
__tablename__ = 'users'
# Define User fields
id = schema.Column(types.Integer(unsigned=True),
schema.Sequence('users_seq_id', optional=True), primary_key=True)
password = schema.Column(types.Unicode(64), nullable=False)
# Define the UserSubset class
class UserSubset(Base):
__tablename__ = 'subsets'
# Define UserSubset fields
id = schema.Column(types.Integer(unsigned=True),
schema.Sequence('subsets_seq_id', optional=True), primary_key=True)
some_short_description = schema.Column(types.Unicode(50), nullable=False)
# Define the subset memberships table
subset_memberships = schema.Table('group_memberships', Base.metadata,
schema.Column('user_id', types.Integer(unsigned=True), ForeignKey('users.id')),
schema.Column('subset_id', types.Integer(unsigned=True), ForeignKey('subsets.id')),
schema.Column('created', types.DateTime(), default=now, nullable=False),
)
Can I connect everything in an associative object ? Or should I change stop using the declarative way ?
What you are using at the moment is just a Many-to-Many-relation. How to work with association objects is described in the docs.
There is also an extension called associationproxy which simplifies the relation.
As you can see in the manual, configuring a one to many relation is really simple:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
addresses = relation("Address", backref="user")
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
email = Column(String(50))
user_id = Column(Integer, ForeignKey('users.id'))
Many to many relations isn't much harder:
There’s nothing special about many-to-many with declarative. The secondary argument to relation() still requires a Table object, not a declarative class. The Table should share the same MetaData object used by the declarative base:
keywords = Table('keywords', Base.metadata,
Column('author_id', Integer, ForeignKey('authors.id')),
Column('keyword_id', Integer, ForeignKey('keywords.id'))
)
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
keywords = relation("Keyword", secondary=keywords)
You should generally not map a class and also specify its table in a many-to-many relation, since the ORM may issue duplicate INSERT and DELETE statements.
Anyway, what you seem to be doing might be better served with inheritance. Of course, there can be complex table relations that will be a pathological case for the declarative way, but this doesn't seem to be one of them.
One more thing, code comments should state what the following code does ans why, not how it does it. Having a # Define the User class comment is almost like having a line of code saying a = 1 # assing value 1 to variable "a".

Categories

Resources