SQLAlchemy Linear One-to-One Relationship - python

I have two classes mapped to two tables respectively.
Ex:
Obj 1: ID (PK), KEY (String(20))
Obj 2: ID (PK), obj_1_id (FK), value (String(20))
I would like to be able to perform obj_1.value = *val*, whereby val is stored on the secon'd table's respective column, instead of obj_1.value.value = val`.
How can I create such relationship, spread/mapped to two tables' columns?
What I want is not one-to-one (object HAS object) but rather map a column of an object to a different table.
Following is what I have tried (following the docs) and it does not work as it creates obj1.value.value = .. instead of direct column mapping
What I have tried:
class Obj1(Base):
__tablename__ == ...
id = ..
key = ..
value = relationship("Obj2", uselist=False, backref="obj1")
class Obj2(Base):
__tablename__ == ...
id = .. # PK
obj_1_id = .. # FK
value = ...

Why not just wrap the python property:
class Obj1(Base):
__tablename__ = 'obj1'
id = Column(Integer, primary_key=True)
key = Column(String(20))
_value_rel = relationship("Obj2", uselist=False, backref="obj1")
#property
def value(self):
return self._value_rel and self._value_rel.value
#value.setter
def value(self, value):
if value is None:
self._value_rel = None
elif self._value_rel is None:
self._value_rel = Obj2(value=value)
else:
self._value_rel.value = value

Related

python - loading #hybrid_property of SQLAlchemy ORM to pandas DataFrame

TLDR DataFrame.from_records() ignores #hybrid_property of sqlalchemy ORM
Hello, I want to be able to populate pandas.DataFrame from sqlalchemy objects.
I have a class which is defined like that:
class Cat_24(Base):
__tablename__ = "cat_24"
ad_id = Column(Integer, primary_key=True, unique=True)
title = Column(String(128))
description = Column(String(8192))
offer_type = Column(String(16))
...
prices = relationship("AdPrice", backref=backref("cat_24"))
...
#hybrid_property
def last_price(self):
if self.prices:
return self.prices[-1].price
else:
return None
#last_price.expression
def last_price(cls):
# return cls.prices[-1].price
return (
select([AdPrice.price])
.where(cls.ad_id == AdPrice.ad_id)
.order_by(AdPrice.price.desc())
.limit(1)
.as_scalar()
)
When I tries to select rows and load them:
tmp = session.query(Cat_24).filter_by(offer_type='Продам').all()
df = pd.DataFrame.from_records( tmp )
I can see only regular Columns in dataframe, last_price will not be loaded.
So question, how to load hybrid_property in DataFrame?
Update ugly solution which I figured out:
def __iter__(self):
for column in self.__table__.columns:
yield column.name, getattr(self, column.name)
if self.aditional_attrs:
for attr in self.aditional_attrs:
yield attr, getattr(self, attr)
self.aditional_attrs is a tuple with hybrid_property names which I want to get, after that tmp query from the above can be converted into list of dictionaries:
tmp = list(map(dict,tmp))

sqlalchemy: how to return list of objects joining 3 tables?

#hybrid_method
# #paginate
def investors(self, **kwargs):
"""All investors for a given Custodian"""
ind_inv_type_id = InvestorType.where(description="Individual").first().id
inv_query = Investor.with_joined(InvestorAddress, InvestmentAddress, CustodianAddress) \
.filter_by(custodians_id=self.id) \
.with_joined(Investment) \
.filter_by(investor_types_id=ind_inv_type_id)
investors = Investor.where(None, False, inv_query, **kwargs)
temp_inv_query = Investor.with_joined(CustodianInvestor, Custodian)\
.filter_by(Custodian.id==self.id)
temp_investors = Investor.where(None, False, temp_inv_query, **kwargs)
return list(set(investors + temp_investors))
# end def investors
# #auth.access_controlled
class InvestorAddress(db.Model, EntityAddressMixin):
# Metadata
__tablename__ = 'investor_addresses'
# Database Columns
investors_id = db.Column(db.ForeignKey("investors.investors_id"),
nullable=False)
investor = db.relationship("Investor", foreign_keys=[investors_id],
backref=db.backref("InvestorAddress"))
# end class InvestorAddress
class InvestmentAddress(db.Model):
"""This model differs from other EntityAddress Models because it links to either an investor_address or an custodian_address."""
# Metadata
__tablename__ = 'investment_addresses'
# Database Columns
address_types_id = db.Column(
db.ForeignKey("address_types.address_types_id"),
nullable=False)
address_type = db.relationship("AddressType",
foreign_keys=[address_types_id],
backref=db.backref("InvestmentAddress"))
investments_id = db.Column(db.ForeignKey("investments.investments_id"),
nullable=False)
investment = db.relationship("Investment",
foreign_keys=[investments_id],
backref=db.backref("InvestmentAddress"))
investor_addresses_id = db.Column(db.ForeignKey(
"investor_addresses.investor_addresses_id"))
investor_address = db.relationship("InvestorAddress",
foreign_keys=[investor_addresses_id],
backref=db.backref("InvestmentAddress"))
custodian_addresses_id = db.Column(db.ForeignKey(
"custodian_addresses.custodian_addresses_id"))
custodian_address = db.relationship("CustodianAddress",
foreign_keys=[custodian_addresses_id],
backref=db.backref("InvestmentAddress")
)
# end class InvestmentAddress
class CustodianAddress(db.Model, EntityAddressMixin):
"""Defines the relationship between a Custodian and their addresses."""
# Metadata
__tablename__ = 'custodian_addresses'
# Database Columns
custodians_id = db.Column(db.ForeignKey(
"custodians.custodians_id"), nullable=False)
custodian = db.relationship("Custodian", foreign_keys=[custodians_id],
backref=db.backref("CustodianAddress"))
# end CustodianAddress
i have an application and this function is supposed to return a list of 'investors' for a given 'Custodian'. Now when it executes i get an error: "sqlalchemy.exc.ArgumentError: mapper option expects string key or list of attributes". The error comes from the 'join' in the 'inv_query'.
I have included my 3 models that im using for the Join.
As described in the documentation provided by you. here
You should provide string arguments(table names) in with_joined. Given you have defined the relationship
Investor.with_joined('investorAddressTable', 'investmentAddressTable, 'custodianAddressTable')
In case you can use session then you can query the ORM classes directly like
session.query(Investor).join(InvestorAddress).join(InvestmentAddress).join(CustodianAddress).all() # will assume you have set the foreign key properly

Flask-Restless dump_to of primary key field

I am running into an issue that may be bug, but want to verify it with the community. I am basically trying to conform to camelcase for transporting data, then underscore for the database.
However, on the person_serializer, flask-restless will not allow an outbound "idPerson" as a result of the dump_to="idPerson". For some reason, it checks that the primary key exists and gets a keyError since the actual key is "id_person", not "idPerson".
Any help would be appreciated.
class Person(Base):
__tablename__ = "person"
id_person = Column(Integer, primary_key=True)
first_name = Column(String(50))
last_name = Column(String(50))
class PersonSchema(Schema):
id_person = fields.Integer(load_from="idPerson",dump_to="idPerson")
first_name = fields.String(load_from="firstName", dump_to="firstName")
last_name = fields.String(load_from="lastName", dump_to="lastName")
#post_load
def make_user(self, data):
return Person(**data)
person_schema = PersonSchema()
def person_serializer(instance):
return person_schema.dump(instance).data
def person_deserializer(data):
return person_schema.load(data).data
KEY ERROR IS BELOW
try:
# Convert the dictionary representation into an instance of the
# model.
instance = self.deserialize(data)
# Add the created model to the session.
self.session.add(instance)
self.session.commit()
# Get the dictionary representation of the new instance as it
# appears in the database.
result = self.serialize(instance)
except self.validation_exceptions as exception:
return self._handle_validation_exception(exception)
# Determine the value of the primary key for this instance and
# encode URL-encode it (in case it is a Unicode string).
pk_name = self.primary_key or primary_key_name(instance)
> primary_key = result[pk_name]
E KeyError: 'idPerson'

SQLAlchemy Declarative: Adding a static text attribute to a column

I am using: SQLAlchemy 0.7.9 and Python 2.7.3 with Bottle 0.11.4. I am an amateur at python.
I have a class (with many columns) derived from declarative base like this:
class Base(object):
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key = True)
def to_dict(self):
serialized = dict((column_name, getattr(self, column_name))
for column_name in self.__table__.c.keys())
return serialized
Base = declarative_base(cls=Base)
class Case(Base):
version = Column(Integer)
title = Column(String(32))
plausible_dd = Column(Text)
frame = Column(Text)
primary_task = Column(Text)
secondary_task = Column(Text)
eval_objectives = Column(Text)
...
I am currently using this 'route' in Bottle to dump out a row/class in json like this:
#app.route('/<name>/:record')
def default(name, record, db):
myClass = getattr(sys.modules[__name__], name)
parms = db.query(myClass).filter(myClass.id == record)
result = json.dumps(([parm.to_dict() for parm in parms]))
return result
My first question is: How can I have each column have some static text that I can use as a proper name such that I can iterate over the columns and get their values AND proper names? For example:
class Case(Base):
version = Column(Integer)
version.pn = "Version Number"
My second question is: Does the following do what I am looking for? I have seen examples of this, but I don't understand the explanation.
Example from sqlalchemy.org:
id = Column("some_table_id", Integer)
My interpretation of the example:
version = Column("Version Number", Integer)
Obviously I don't want a table column to be created. I just want the column to have an "attribute" in the generic sense. Thank you in advance.
info dictionary could be used for that. In your model class define it like this:
class Case(Base):
version = Column(Integer, info={'description': 'Version Number'})
Then it can accessed as the table column property:
desc = Case.__table__.c.version.info.get('description', '<no description>')
Update
Here's one way to iterate through all the columns in the table and get their names, values and descriptions. This example uses dict comprehension, which is available since Python 2.7.
class Case(Base):
# Column definitions go here...
def as_dict(self):
return {c.name: (getattr(self, c.name), c.info.get('description'))
for c in self.__table__.c}

Get the relationship metadata from sqlalchemy

I have a very simple User class definition:
class User(Base):
implements(interfaces.IUser)
__tablename__ = 'users'
#Fields description
id = Column(Integer, primary_key=True)
client_id = Column(Integer, ForeignKey('w2_client.id'))
client = relationship("Client", backref=backref('users', order_by=id))
I want to generate automatically a GUI to edit the object User (and other type of class). So I need to get all the meta data of the table, for example, I can do:
for c in User.__table__.columns:
print c.name, c.type, c.nullable, c.primary_key, c.foreign_keys
But I can not get any information about the relationship "client", the c.foreign_keys just shows me the table related to the foreign_keys but not the attribute "client" I've defined.
Please let me know if my question is not clear
It's true that is not readily available. I had to come up with my own function after some reverse-engineering.
Here is the metadata that I use. I little different than what you are are looking for, but perhaps you can use it.
# structure returned by get_metadata function.
MetaDataTuple = collections.namedtuple("MetaDataTuple",
"coltype, colname, default, m2m, nullable, uselist, collection")
def get_metadata_iterator(class_):
for prop in class_mapper(class_).iterate_properties:
name = prop.key
if name.startswith("_") or name == "id" or name.endswith("_id"):
continue
md = _get_column_metadata(prop)
if md is None:
continue
yield md
def get_column_metadata(class_, colname):
prop = class_mapper(class_).get_property(colname)
md = _get_column_metadata(prop)
if md is None:
raise ValueError("Not a column name: %r." % (colname,))
return md
def _get_column_metadata(prop):
name = prop.key
m2m = False
default = None
nullable = None
uselist = False
collection = None
proptype = type(prop)
if proptype is ColumnProperty:
coltype = type(prop.columns[0].type).__name__
try:
default = prop.columns[0].default
except AttributeError:
default = None
else:
if default is not None:
default = default.arg(None)
nullable = prop.columns[0].nullable
elif proptype is RelationshipProperty:
coltype = RelationshipProperty.__name__
m2m = prop.secondary is not None
nullable = prop.local_side[0].nullable
uselist = prop.uselist
if prop.collection_class is not None:
collection = type(prop.collection_class()).__name__
else:
collection = "list"
else:
return None
return MetaDataTuple(coltype, str(name), default, m2m, nullable, uselist, collection)
def get_metadata(class_):
"""Returns a list of MetaDataTuple structures.
"""
return list(get_metadata_iterator(class_))
def get_metadata_map(class_):
rv = {}
for metadata in get_metadata_iterator(class_):
rv[metadata.colname] = metadata
return rv
But it doesn't have the primary key. I use a separate function for that.
mapper = class_mapper(ORMClass)
pkname = str(mapper.primary_key[0].name)
Perhaps I should put the primary key name in the metadata.

Categories

Resources