sqlalchemy / table setup - python

I have items, warehouses, and items are in warehouses.
So I have table that has information about items (sku, description, cost ...) and a table that describes warehouses(location, code, name, ...). Now I need a way to store inventory so that I know I have X items in warehouse Y. An item can be in any warehouse.
How would I go about setting up the relationship between them and storing the qty?
class Item(DeclarativeBase):
__tablename__ = 'items'
item_id = Column(Integer, primary_key=True,autoincrement=True)
item_code = Column(Unicode(35),unique=True)
item_description = Column(Unicode(100))
item_long_description = Column(Unicode())
item_cost = Column(Numeric(precision=13,scale=4))
item_list = Column(Numeric(precision=13,scale=2))
def __init__(self,code,description,cost,list):
self.item_code = code
self.item_description = description
self.item_cost = cost
self.item_list = list
class Warehouse(DeclarativeBase):
__tablename__ = 'warehouses'
warehouse_id = Column(Integer, primary_key=True, autoincrement=True)
warehouse_code = Column(Unicode(15),unique=True)
warehouse_description = Column(Unicode(55))
If I am correct I would setup the many to many using an intermediate table something like ...
item_warehouse = Table(
'item_warehouse', Base.metadata,
Column('item_id', Integer, ForeignKey('items.item_id')),
Column('warehouse_id', Integar, ForeignKey('warehouses.warehouse_id'))
)
But i would need to start the qty available on this table but since its not its own class I am not sure how that would work.
What would be the "best" practice for modeling this and having it usable in my app?

Model:
As mentioned by #Lafada, you need an Association Object. As such I would create a SA-persistent object and not only a table:
class ItemWarehouse(Base):
# version-1:
__tablename__ = 'item_warehouse'
__table_args__ = (PrimaryKeyConstraint('item_id', 'warehouse_id', name='ItemWarehouse_PK'),)
# version-2:
#__table_args__ = (UniqueConstraint('item_id', 'warehouse_id', name='ItemWarehouse_PK'),)
#id = Column(Integer, primary_key=True, autoincrement=True)
# other columns
item_id = Column(Integer, ForeignKey('items.id'), nullable=False)
warehouse_id = Column(Integer, ForeignKey('warehouses.id'), nullable=False)
quantity = Column(Integer, default=0)
This covers the model requirement with the following:
added a PrimaryKey
added a UniqueConstraint covering the (item_id, warehouse_id) pairs.
In the code above this is solved in two ways:
version-1: uses composite primary key (which must be unique)
version-2: uses simple primary key, but also adds an explicit unique constraint [I personally prefer this option]
Relationship: Association Object
Now. You can use the Association Object as is, which will look similar to this:
w = Warehouse(...)
i = Item(name="kindle", price=...)
iw = ItemWarehouse(quantity=50)
iw.item = i
w.items.append(i)
Relationship: Association Proxy extension
or, you could go one step further and use the Composite Association Proxies example, and you may configure dictionary-like access to the association object similar to this:
w = Warehouse(...)
i = Item(name="kindle", price=...)
w[i] = 50 # sets the quantity to 50 of item _i_ in warehouse _w_
i[w] = 50 # same as above, if you configure it symmetrically
Beware: the code for the relationships definition might look really not easily readable, but the usage pattern is really nice. So if this option is too much to digest, I would start with Association Object with maybe some helper functions to add/get/update the item stocks, and eventually move to the Association Proxy extesion.

You have to use "Association Object".
I try to give you hint for your problem you have to create table like you mention in your question
item_warehouse = Table( 'item_warehouse',
Base.metadata,
Column('item_id',
Integer,
ForeignKey('items.item_id')
),
Column('warehouse_id',
Integar,
ForeignKey('warehouses.warehouse_id')
),
Column('qty',
Integer,
default=0,
),
)
Now you can add warehouse, item and qty in single object and you have to write method which will take warehouse_id and item_id and get the sum of qty for those itmes.
Hope this will help you to solve your problem.

Related

Many-to-many join table with additional field in Flask

I have two tables, Products and Orders, inside my Flask-SqlAlchemy setup, and they are linked so an order can have several products:
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(
"Products", secondary=order_products_table, backref="orders")
....
linked via:
order_products_table = db.Table("order_products_table",
db.Column('orders_guid', db.String(36), db.ForeignKey('orders.guid')),
db.Column('products_id', db.Integer, db.ForeignKey('products.id'))
# db.Column('license', dbString(36))
)
For my purposes, each product in an order will receive a unique license string, which logically should be added to the order_products_table rows of each product in an order.
How do I declare this third license column on the join table order_products_table so it gets populated it as I insert an Order?
I've since found the documentation for the Association Object from the SQLAlchemy docs, which allows for exactly this expansion to the join table.
Updated setup:
# Instead of a table, provide a model for the JOIN table with additional fields
# and explicit keys and back_populates:
class OrderProducts(db.Model):
__tablename__ = 'order_products_table'
orders_guid = db.Column(db.String(36), db.ForeignKey(
'orders.guid'), primary_key=True)
products_id = db.Column(db.Integer, db.ForeignKey(
'products.id'), primary_key=True)
order = db.relationship("Orders", back_populates="products")
products = db.relationship("Products", back_populates="order")
licenses = db.Column(db.String(36), nullable=False)
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
order = db.relationship(OrderProducts, back_populates="order")
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(OrderProducts, back_populates="products")
....
What is really tricky (but also shown on the documentation page), is how you insert the data. In my case it goes something like this:
o = Orders(...) # insert other data
for id in products:
# Create OrderProducts join rows with the extra data, e.g. licenses
join = OrderProducts(licenses="Foo")
# To the JOIN add the products
join.products = Products.query.get(id)
# Add the populated JOIN as the Order products
o.products.append(join)
# Finally commit to database
db.session.add(o)
db.session.commit()
I was at first trying to populate the Order.products (or o.products in the example code) directly, which will give you an error about using a Products class when it expects a OrderProducts class.
I also struggled with the whole field naming and referencing of the back_populates. Again, the example above and on the docs show this. Note the pluralization is entirely to do with how you want your fields named.

Using SQLAlchemy, Accessing Latest Value for Foreign Key as Attribute

I'm sure I'm wording this improperly, mainly because I'm not quite sure how I'm looking to achieve my goal here...
I've got an SQL Alchemy db class that, among other things, stores a value for the price of a commodity. I need to do two things with this price:
Access the latest price of a Commodity at any given moment
Access historical Commodity.price changes
Currently, my models look like this:
class Commodity(Base):
# Create Tablename
__tablename__ = "commodities"
# Defaults
id = Column(Integer, primary_key=True, autoincrement=True)
created = Column(DateTime, default=datetime.now)
updated = Column(DateTime, onupdate=datetime.now)
# Uniques
name = Column(String(), unique=True)
price = ???
class Price(Base):
# Create Tablename
__tablename__ = "prices"
# Defaults
id = Column(Integer, primary_key=True, autoincrement=True)
# Uniques
date = Column(DateTime, default=datetime.now)
commodity = Column(ForeignKey(Commodity))
price = Column(Integer())
In the Commodity class you'll note I have a ??? value as a placeholder for the price. I'd like to be able to access the price as Commodity.price in a way that would give me the latest value for that Commodity, identical to the following query:
Price.query.order_by('updated desc').limit(1)
I'm very new to SQL Alchemy, and appreciate that my approach may be wrong in general. Any help would be much-appreciated.
With my current understanding, I am planning to create an entry in the prices table for each update and simply update the Commodity.price value during the process. It seems a bit more elegant to use a function to get the Commodity.price via the prices table though. Is such an approach possible/recommended?
EDIT 1:
Essentially, I'm wondering how to do something like this:
class Commodity(Base):
...
price = Column(ForeignKey(Price.query.order_by('updated desc').limit(1)))
Just to clarify, this isn't (to my understanding) a one-to-many relationship but, rather, a many-to-many.

SQLAlachmey: ORM filter to match all items in a list, not any

I want to search a SQLAlachmey list (via an association table) and match on multiple items within it via a filter.
I already reviewed this question but I am looking to accomplish this via the ORM filter only (and the second answer is not via an association table).
Database table setup:
tag_ast_table = Table('tag_association',
Base.metadata,
Column('file_id', Integer, ForeignKey('files.id')),
Column('tag_id', Integer, ForeignKey('tags.id')),
PrimaryKeyConstraint('file_id', 'tag_id'))
class File(Base):
__tablename__ = 'files'
id = Column(Integer, primary_key=True)
tags = relationship("Tag", secondary=tag_ast_table)
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
tag = Column(String)
Current filter to match any I would like to modify to match all:
query = db.query(File).filter(File.tags.any(Tag.tag.in_(my_list))).all()
A reasonable approach to this in SQL (alluded to in your link) is to use having count(distinct tags.id) = <your number of tags>.
So the query needs 2 things: it needs an in that looks for your list of tags, and it needs a having that looks for the full count being present.
query = (
session.query(File)
.join(File.tags)
.filter(Tag.tag.in_(search_tags))
.group_by(File)
.having(func.count(distinct(Tag.id)) == len(search_tags))
)
As an edge case, if search_tags is an empty list you won't get any results, so best to check for that first.

marking columns in sqlalchemy tables

I'd like to add a custom attribute "searchable" to the columns in an sqlalchemy model. The purpose is to extract data for just these columns (using ModelFoo.__ table__.columns) and put the data into solr. Is there a way I can mark certain columns using a custom attribute?
class ModelFoo(AppBase):
__tablename__ = 'foo'
id = Column("id", Integer, primary_key=True, autoincrement=True)
os = Column(String, nullable=False, searchable=True)
platform = Column(String, searchable=True)
by default, I get the following error when I try the above:
sqlalchemy.exc.ArgumentError: Unknown arguments passed to Column: ['searchable']
I am looking for a generic way to add only "searchable" columns to solr, something along these lines:
for table in Base.metadata.tables.values():
keys = [str(key) for key in table.columns if key.searchable]
solr.add(session.query(*keys).all())
in the above code, I am looking for some short solution or alternative to get "key.searchable" to work. Hope this clarifies the question.
I solved this using a separate attribute in model:
class ModelFoo(Base):
__tablename__ = 'foo'
id = Column("id", Integer, primary_key=True, autoincrement=True)
os = Column(String, nullable=False)
platform = Column(String)
search_cols = ["os", "value"]
for k, v in list(Base._decl_class_registry.items()):
if (hasattr(v, "search_cols")):
cols = [getattr(v, val) for val in v.search_cols]
query = sess.query(*cols)
solr.add(query.all())
I'm a little unclear on your question but it looks like your are trying to do something like this from the link you provided:
si.add(Book.objects.all())
Where Book.objects.all() is a list of records from an ORM mapped table. Note that the docs say a list of objects is also acceptable. I think to solution here is to use the SQLAlchemy query method to build records with only the fields you want. Using your example with would look like this:
si.add(session.query(ModelFoo.os, ModelFoo.platform).all())

How to set up complex condition composite foreign key in SQLAlchemy

Here's my ORM entity class. The primary key is composite cause 'id_string' may be the same for different users (identified by uid). One thing I understood from Postgres SQL error when creating a table based on this class (
ProgrammingError: (ProgrammingError) there is no unique constraint matching given keys for referenced table "sync_entities"
) is that I need to add something to parent_id_string's ForeignKey() argument. And that something is, I think, the current record's uid.
Do you suggest to try using different primary key (autoincrementing integer) or there is some other way?
class SyncEntity(Base):
__tablename__ = 'sync_entities'
__table_args__ = (ForeignKeyConstraint(['uid'], ['users.uid'], ondelete='CASCADE'), {})
uid = Column(BigInteger, primary_key=True)
id_string = Column(String, primary_key=True)
parent_id_string = Column(String, ForeignKey('sync_entities.id_string'))
children = relation('SyncEntity',
primaryjoin=('sync_entities.c.id_string==sync_entities.c.parent_id_string'),
backref=backref('parent', \
remote_side=[id_string]))
# old_parent_id = ...
version = Column(BigInteger)
mtime = Column(BigInteger)
ctime = Column(BigInteger)
name = Column(String)
non_unique_name = Column(String)
sync_timestamp = Column(BigInteger)
server_defined_unique_tag = Column(String)
position_in_parent = Column(BigInteger)
insert_after_item_id = Column(String, ForeignKey('sync_entities.id_string'))
insert_after = relation('SyncEntity',
primaryjoin=('sync_entities.c.id_string==sync_entities.c.insert_after_item_id'),
remote_side=[id_string])
deleted = Column(Boolean)
originator_cache_guid = Column(String)
originator_client_item_id = Column(String)
specifics = Column(LargeBinary)
folder = Column(Boolean)
client_defined_unique_tag = Column(String)
ordinal_in_parent = Column(LargeBinary)
You know, primary key being an auto-incremented integer is usually the best approach. Any values that seem to be unique in system, may turn out to be duplicated in future. If you relied on their uniqueness you're in deep trouble.
However, if there is a reason to require certain pair (or triple) of values in each row to be unique, just add constraint to your table, but use auto-increment integer as primary key. Then if requirements change, you can alter/remove/relax your unique constraint without making changes elsewhere.
Also - if you're using simple integer keys, your joins are simpler and can be performed faster by DBMS.
I think I came up with a good idea. Just need to create complex foreign key constructs in the __tableargs__ member like (parent_id_string, uid) and (insert_after_item_id, uid), modifying the primaryjoin statements accordingly.

Categories

Resources