I'm trying to set up a self-referential many-to-many database via an association object (since I need to add a value in the relationship).
I have searched all over Stack Exchange, Google and the SQLAlchemy documentation, but I haven't found a single similar instance.
The model definitions are (Python3.8):
class Recipe(db.Model):
id = db.Column(db.Integer, primary_key=True)
base_id = db.Column(db.Integer, db.ForeignKey('color.id'))
ingredients = db.relationship('Color',
backref='recipe',
cascade='save-update')
quantity = db.Column(db.Integer)
def __init__(self, ingredient, quantity):
print(f'{ingredient=}\n{quantity=}')
self.ingredients = ingredient
self.quantity = quantity
def __repr__(self):
return f'Recipe({self.ingredients} x{self.quantity})'
class Color(db.Model):
id = db.Column(db.Integer, primary_key=True)
medium = db.Column(db.String(2), nullable=False)
name = db.Column(db.String(50), nullable=False, unique=True)
pure = db.Column(db.Boolean, default=False, nullable=False)
_recipe = db.relationship(Recipe,
primaryjoin=id==Recipe.base_id,
join_depth=1)
recipe = association_proxy('_recipe', 'ingredients',
creator = lambda _: _)
swatch = db.Column(db.String(25), nullable=False, unique=True)
def __init__(self, medium, name, pure, *, recipe=None, swatch):
self.medium = medium.upper()
self.name = name
self.pure = pure
self.swatch = swatch
if self.pure:
self.recipe = [Recipe(self, 1)]
else:
for ingredient, quantity in recipe:
self.recipe.append(Recipe(ingredient, quantity))
def __repr__(self):
return f'Color({self.name})'
I am using a SQLite database.
I would expect to have a column for each attribute in both tables, but for some reason, this is the only table creation SQL generated:
CREATE TABLE color (
id INTEGER NOT NULL,
medium VARCHAR(2) NOT NULL,
name VARCHAR(50) NOT NULL,
pure BOOLEAN NOT NULL,
swatch VARCHAR(25) NOT NULL,
PRIMARY KEY (id),
UNIQUE (name),
CHECK (pure IN (0, 1)),
UNIQUE (swatch)
)
CREATE TABLE recipe (
id INTEGER NOT NULL,
base_id INTEGER,
quantity INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(base_id) REFERENCES color (id)
)
I am receiving no errors. If I use the cli to manually input records, all defined columns save, though not entirely correctly. I think that may be due to the missing columns, though, and will post it as a separate issue if needed, later.
Related
I've the following class
class Comision(Base):
__tablename__ = 'comision'
id = Column(Integer, primary_key=True)
key = Column(Integer, default=process())
in the column key i've the def process and i need to make some operations with the primary key of that table, it is possible to pass it like argument?
Try
key = Column(Integer, default=process)
or
key = Column(Integer, default=lambda:process())
Defined as key = Column(Integer, default=process()), process() is called once only when class Comision is defined.
Take a look at Context-Sensitive Default Functions in http://docs.sqlalchemy.org/en/rel_0_8/core/defaults.html for more detail.
I've been issues setting the "Character Set" and "Collation" for my tables in MySQL created using SQLAlchemy.
In particular, tables were always created using "latin1" for both the "Character Set" and "Collation".
Setting the "charset" in the URL, didn't solve the issue:
f'mysql+pymysql://{_user}:{_pwd}#{_host}:{_port}/{_db}?charset=utf8mb4',
Neither, did create_engine(...,encoding='utf8').
Only by setting the following __table_args__, was the table created with support for UTF-8:
__table_args__ = (
{'mysql_default_charset': 'utf8',
'mysql_collate': 'utf8_bin'}
)
How can I set the above table arguments for all tables in the database without having to specify them explicitly as __table_args__ for each of them?
Full example:
class Match(Base):
__tablename__ = 'stash_match'
id = Column(Integer, primary_key=True, autoincrement=True)
blob_id = Column(String(40))
text = Column(Text, nullable=False)
regex = Column(Text, nullable=False)
blob = relationship('Blob', back_populates='matches')
__table_args__ = (
ForeignKeyConstraint([blob_id], [Blob.id], onupdate='CASCADE', ondelete='CASCADE'),
{'mysql_default_charset': 'utf8',
'mysql_collate': 'utf8_bin'}
)
As described in the documentation here you can augment the declarative Base class to customize the default behaviour:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
connection_uri = (
"mysql+pymysql://root:toot#localhost:3307/mydb?charset=UTF8mb4"
)
engine = sa.create_engine(connection_uri, echo=True,)
class Base(object):
__table_args__ = {
"mysql_default_charset": "utf16",
"mysql_collate": "utf16_icelandic_ci",
}
Base = declarative_base(cls=Base)
class Team(Base):
__tablename__ = "team"
prov = sa.Column(sa.String(2), primary_key=True)
city = sa.Column(sa.String(20), primary_key=True)
team_name = sa.Column(sa.String(20))
def __repr__(self):
return f"<Team(prov='{self.prov}', city='{self.city}')>"
Base.metadata.create_all(engine)
The DDL that gets rendered is
CREATE TABLE team (
prov VARCHAR(2) NOT NULL,
city VARCHAR(20) NOT NULL,
team_name VARCHAR(20),
PRIMARY KEY (prov, city)
)DEFAULT CHARSET=utf16 COLLATE utf16_icelandic_ci
I have two models: shipping model that is having a foreign key carrierID and it is referencing the other model Carrier. When I want to add a new shipping I am getting the aforementioned error. What I know, I don't have carrierID_id field but I don't why I getting this error.
Here is the structure of my models:
class Carrier(models.Model):
class Meta:
verbose_name_plural='carriers'
carrierID=models.CharField(max_length=15, unique=True)
carrierName=models.CharField(max_length=50)
carrierTelephoneNumber=models.CharField(max_length=14)
carrierAddress=models.TextField()
def __str__(self):
return '{}{}{}{}'.format(self.carrierID,' ' , self.carrierName,' ', self.carrierTelephoneNumber,' ',self.carrierAddress)
class Shipping(models.Model):
shippingID = models.AutoField(primary_key=True)
carrierID = models.ForeignKey(Carrier, on_delete=models.CASCADE)
shippingDate = models.DateTimeField()
productName=models.CharField(max_length=30)
shippingAddress = models.TextField()
sendTo = models.CharField(max_length=50)
shippingStatus = models.BooleanField(default=False)
promisedDate=models.DateTimeField()
comment = models.TextField()
The structure of shippingForm is as below:
class ShippingForm(forms.ModelForm):
carrier = Carrier.objects.only('carrierID')
print(carrier)
carrierID = forms.ModelChoiceField(carrier)
shippingDate = forms.DateTimeField(required=False)
productName = forms.CharField(max_length=30, required=False)
shippingAddress = forms.Textarea()
sendTo = forms.CharField(max_length=50)
shippingStatus = forms.BooleanField(required=False)
promisedDate = forms.DateTimeField(required=False)
comment = forms.Textarea()
class Meta:
model=Shipping
fields=('carrierID','shippingDate', 'productName','shippingAddress', 'sendTo','shippingStatus','promisedDate', 'comment',)
Below are my MySQL tables:
Here is the SQL of the shipping.
create table pages_shipping(shippingID int auto_increment,
carrierID int(11),
shippingDate date,
prductName varchar(50),
shippingAddress text,
sendTo varchar(50),
shippingStatus boolean,
promisedDate date,
comment text,
primary key(shippingID),
foreign key(carrierID) REFERENCES pages_Carrier(id) ON DELETE CASCADE);
Please assist
I got what is happening. It is because when I run py manage.py makemigrations command, the field carrierID is not added. Unfortunately, I don't know to force Django to add it
class AccountIndexes(Base):
__tablename__ = 'account_indexes'
id = Column(Integer, primary_key = True)
user_id = Column(Integer, nullable=False, unique=True, index=True)
full_name = Column(String(255), nullable=True)
__table_args__ = {'mysql_engine':'MyISAM'}
That's my table. How do I make a "full text index" on full_name? (not a normal index)
If you can check the code in Index of sqlalchemy.schema then they wrote
def __init__(self, name, *columns, **kw):
"""Construct an index object.
:param name:
The name of the index
:param \*columns:
Columns to include in the index. All columns must belong to the same
table.
:param unique:
Defaults to False: create a unique index.
:param \**kw:
Other keyword arguments may be interpreted by specific dialects.
"""
self.table = None
# will call _set_parent() if table-bound column
# objects are present
ColumnCollectionMixin.__init__(self, *columns)
self.name = name
self.unique = kw.pop('unique', False)
self.kwargs = kw
Now in __init__ of the Index they only check the unique. I think to generate fulltext index you have to use DDL manipulation functionality of sqlalchemy.
I have a star-schema architectured database that I want to represent in SQLAlchemy. Now I have the problem on how this can be done in the best possible way. Right now I have a lot of properties with custom join conditions, because the data is stored in different tables.
It would be nice if it would be possible to re-use the dimensions for different fact tablesw but I haven't figured out how that can be done nicely.
A typical fact table in a star schema contains foreign key references to all dimension tables, so usually there wouldn't be any need for custom join conditions - they are determined automatically from foreign key references.
For example a star schema with two fact tables would look like:
Base = declarative_meta()
class Store(Base):
__tablename__ = 'store'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
class Product(Base):
__tablename__ = 'product'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
class FactOne(Base):
__tablename__ = 'sales_fact_one'
store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
units_sold = Column('units_sold', Integer, nullable=False)
store = relation(Store)
product = relation(Product)
class FactTwo(Base):
__tablename__ = 'sales_fact_two'
store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
units_sold = Column('units_sold', Integer, nullable=False)
store = relation(Store)
product = relation(Product)
But suppose you want to reduce the boilerplate in any case. I'd create generators local to the dimension classes which configure themselves on a fact table:
class Store(Base):
__tablename__ = 'store'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
#classmethod
def add_dimension(cls, target):
target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
target.store = relation(cls)
in which case usage would be like:
class FactOne(Base):
...
Store.add_dimension(FactOne)
But, there's a problem with that. Assuming the dimension columns you're adding are primary key columns, the mapper configuration is going to fail since a class needs to have its primary keys set up before the mapping is set up. So assuming we're using declarative (which you'll see below has a nice effect), to make this approach work we'd have to use the instrument_declarative() function instead of the standard metaclass:
meta = MetaData()
registry = {}
def register_cls(*cls):
for c in cls:
instrument_declarative(c, registry, meta)
So then we'd do something along the lines of:
class Store(object):
# ...
class FactOne(object):
__tablename__ = 'sales_fact_one'
Store.add_dimension(FactOne)
register_cls(Store, FactOne)
If you actually have a good reason for custom join conditions, as long as there's some pattern to how those conditions are created, you can generate that with your add_dimension():
class Store(object):
...
#classmethod
def add_dimension(cls, target):
target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
target.store = relation(cls, primaryjoin=target.store_id==cls.id)
But the final cool thing if you're on 2.6, is to turn add_dimension into a class decorator. Here's an example with everything cleaned up:
from sqlalchemy import *
from sqlalchemy.ext.declarative import instrument_declarative
from sqlalchemy.orm import *
class BaseMeta(type):
classes = set()
def __init__(cls, classname, bases, dict_):
klass = type.__init__(cls, classname, bases, dict_)
if 'metadata' not in dict_:
BaseMeta.classes.add(cls)
return klass
class Base(object):
__metaclass__ = BaseMeta
metadata = MetaData()
def __init__(self, **kw):
for k in kw:
setattr(self, k, kw[k])
#classmethod
def configure(cls, *klasses):
registry = {}
for c in BaseMeta.classes:
instrument_declarative(c, registry, cls.metadata)
class Store(Base):
__tablename__ = 'store'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
#classmethod
def dimension(cls, target):
target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
target.store = relation(cls)
return target
class Product(Base):
__tablename__ = 'product'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
#classmethod
def dimension(cls, target):
target.product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
target.product = relation(cls)
return target
#Store.dimension
#Product.dimension
class FactOne(Base):
__tablename__ = 'sales_fact_one'
units_sold = Column('units_sold', Integer, nullable=False)
#Store.dimension
#Product.dimension
class FactTwo(Base):
__tablename__ = 'sales_fact_two'
units_sold = Column('units_sold', Integer, nullable=False)
Base.configure()
if __name__ == '__main__':
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
sess = sessionmaker(engine)()
sess.add(FactOne(store=Store(name='s1'), product=Product(name='p1'), units_sold=27))
sess.commit()